From 89eb9ec13738efc57c8e45de1e0c720f85a21e05 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:22:02 -0400 Subject: [PATCH 01/39] feat: remove extensible schemas and update documentation --- docs/USAGE.md | 62 ++++------ src/main.ts | 34 +++--- src/refinements/index.ts | 14 +-- src/schemas/sdo/analytic.schema.ts | 10 +- src/schemas/sdo/asset.schema.ts | 17 ++- src/schemas/sdo/campaign.schema.ts | 31 +++-- src/schemas/sdo/collection.schema.ts | 7 +- src/schemas/sdo/data-component.schema.ts | 9 +- src/schemas/sdo/data-source.schema.ts | 20 ++-- src/schemas/sdo/detection-strategy.schema.ts | 8 +- src/schemas/sdo/group.schema.ts | 21 ++-- src/schemas/sdo/identity.schema.ts | 16 +-- src/schemas/sdo/index.ts | 112 ++++++++----------- src/schemas/sdo/log-source.schema.ts | 10 +- src/schemas/sdo/malware.schema.ts | 37 +++--- src/schemas/sdo/matrix.schema.ts | 9 +- src/schemas/sdo/mitigation.schema.ts | 10 +- src/schemas/sdo/software.schema.ts | 18 ++- src/schemas/sdo/stix-bundle.schema.ts | 50 ++++----- src/schemas/sdo/tactic.schema.ts | 13 +-- src/schemas/sdo/technique.schema.ts | 43 ++++--- src/schemas/sdo/tool.schema.ts | 28 +++-- src/schemas/sro/relationship.schema.ts | 21 ++-- 23 files changed, 247 insertions(+), 353 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index ef672177..8d5dadba 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -105,68 +105,44 @@ The library provides Zod schemas for all ATT&CK object types, enabling validatio ### Accessing Schemas -Schemas are available under the `schemas` directory. You can import them directly from the package root: +Schemas and their associated TypeScript types are available under the `schemas` directory. You can import them directly from the package root: ```typescript -import { tacticSchema } from '@mitre-attack/attack-data-model'; +import { campaignSchema } from '@mitre-attack/attack-data-model'; +import type { Campaign } from '@mitre-attack/attack-data-model'; ``` -Notably, there are two versions of each ATT&CK type available: - -* One "extensible" schema (denoted by its `extensible` prefix) that returns a `ZodType` -* One "standard" schema that returns a `ZodEffect` - -The extensible schemas (e.g., `extensibleCampaignSchema`) return a `ZodType` by intentionally omitting all Zod refinements. You should use these schemas if you are looking to extend or modify the baseline schema behaviors. - -For example, let's say you wish to augment ATT&CK campaigns with your own custom fields—you can do this with the `extensibleCampaignSchema` as follows: - -```typescript -// Using the extensible schema for type definition or extension -import { extensibleCampaignSchema } from '@mitre-attack/attack-data-model'; -const myCustomCampaignSchema = extensibleCampaignSchema.extend({ /* additional fields */ }); -``` +Many of the ATT&CK schemas use Zod refinements. We leverage refinements to execute advanced validation checks (e.g., validating that the first reference in `external_references` contains a valid ATT&CK ID). -This would not work for the "standard" `campaignSchema`. +Unfortunately, in current versions of Zod, if a schema is modified with one of the object methods (`pick`, `omit`, `extend`), the refinements will be discarded. -`campaignSchema` returns a `ZodEffect` by nature of employing Zod refinements. We leverage refinements to execute advanced validation checks (e.g., validating that the first reference in `external_references` contains a valid ATT&CK ID). You can use the refined schemas like you would any other Zod schema, with the added disclaimer that they are less extensible than their aforementioned counterparts: +For example, let's say you wish to augment ATT&CK campaigns with your own custom fields: ```typescript -// Using the refined schema for validation import { campaignSchema } from '@mitre-attack/attack-data-model'; -const validCampaign = campaignSchema.parse(rawCampaignData); +const myCustomCampaignSchema = campaignSchema.extend({ /* additional fields */ }); ``` -And don't worry—you can still use these refinements with your custom schemas. Each ATT&CK refinement is decoupled so they can be used modularly. They are exported as factory functions in the `refinements` sub-package: +`myCustomCampaignSchema` would not be valid, as it is missing the refinements that were originally present in `campaignSchema`. -```typescript -// Step 1 - import the refinements you want to use -import { createFirstAliasRefinement, createCitationsRefinement } from '@mitre-attack/attack-data-model'; +You can still use the original refinements in your custom schemas, it will just take an extra step. Each ATT&CK refinement is decoupled so they can be used modularly. They are exported as factory functions in the `refinements` sub-package: -// Step 2 - initialize the refinements -const validateFirstAlias = createFirstAliasRefinement(); -const validateCitations = createCitationsRefinement(); +```typescript +// Import the original schema, and the refinements you want to use +import { campaignSchema, createFirstAliasRefinement, createCitationsRefinement } from '@mitre-attack/attack-data-model'; -// Step 3 - apply a single refinement that combines the imported refinements -const myCustomCampaignSchema = extensibleCampaignSchema +// Apply a single refinement that combines the imported refinements +const myCustomCampaignSchema = campaignSchema .extend({ /* additional fields */ }) - .superRefine((val, ctx) => { - validateFirstAlias(val, ctx); - validateCitations(val, ctx); + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createCitationsRefinement()(ctx); }); ``` -Notably, all ATT&CK schemas export only one TypeScript type, named in accordance with the refined schema, but inferred from the extensible schema. Since the refinements only add validation rules (rejected values) without changing the shape of valid data, a single type definition is sufficient: - -```typescript -// An extensible schema for customizing or augmenting ATT&CK campaigns -import { extensibleCampaignSchema } from '@mitre-attack/attack-data-model'; - -// An inelastic but fully implemented schema for validating ATT&CK campaigns -import { campaignSchema } from '@mitre-attack/attack-data-model'; +You will have to look in the original schema file, in this case [/src/schemas/sdo/campaign.schema.ts](/src/schemas/sdo/campaign.schema.ts) to see which refinements, if any, should be applied to the ATT&CK schema that you wish to extend. -// One type definition for *all* ATT&CK campaigns (custom or otherwise) -import type { Campaign } from '@mitre-attack/attack-data-model'; -``` +This [GitHub issue](https://github.com/colinhacks/zod/issues/4874) and [pull request](https://github.com/colinhacks/zod/pull/4865) describe the behavior and an upcoming `safeExtend` method that will allow you to extend the ATT&CK schemas without having to reapply the refinements. ### Validating Data diff --git a/src/main.ts b/src/main.ts index 30a6ec1a..6d5cce8a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,31 +2,31 @@ import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import { - type StixBundle, + stixBundleSchema, type AttackObject, - extensibleStixBundleSchema, type AttackObjects, + type StixBundle, } from './schemas/sdo/stix-bundle.schema.js'; import { - techniqueSchema, - tacticSchema, - matrixSchema, - mitigationSchema, - relationshipSchema, - dataSourceSchema, + analyticSchema, + assetSchema, + campaignSchema, + collectionSchema, dataComponentSchema, + dataSourceSchema, + detectionStrategySchema, groupSchema, - malwareSchema, - toolSchema, - markingDefinitionSchema, identitySchema, - collectionSchema, - campaignSchema, - assetSchema, logSourceSchema, - detectionStrategySchema, - analyticSchema, + malwareSchema, + markingDefinitionSchema, + matrixSchema, + mitigationSchema, + relationshipSchema, + tacticSchema, + techniqueSchema, + toolSchema, } from './schemas/index.js'; import { @@ -199,7 +199,7 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO const validObjects: AttackObject[] = []; // Validate the bundle's top-level properties - const baseBundleValidationResults = extensibleStixBundleSchema + const baseBundleValidationResults = stixBundleSchema .pick({ id: true, type: true, diff --git a/src/refinements/index.ts b/src/refinements/index.ts index b60a761e..acad7c7a 100644 --- a/src/refinements/index.ts +++ b/src/refinements/index.ts @@ -1,10 +1,12 @@ -import { z } from 'zod/v4'; +import { attackIdExamples, attackIdPatterns } from '@/schemas/common/attack-id.js'; import { attackDomainSchema, - type AttackObject, type Aliases, + type AttackObject, type ExternalReferences, type KillChainPhase, + type Relationship, + type RelationshipType, type StixBundle, type Technique, type XMitreDataSources, @@ -19,10 +21,8 @@ import { type XMitreRemoteSupport, type XMitreSystemRequirements, type XMitreTacticType, - type Relationship, - type RelationshipType, } from '@/schemas/index.js'; -import { attackIdPatterns, attackIdExamples } from '@/schemas/common/attack-id.js'; +import { z } from 'zod/v4'; /** * Creates a refinement for validating that the first alias matches the object's name @@ -66,7 +66,7 @@ export function createFirstAliasRefinement() { * @example * ```typescript * const validateFirstXMitreAlias = createFirstXMitreAliasRefinement(); - * const schema = extensibleSchema.superRefine(validateFirstXMitreAlias); + * const schema = baseSchema.superRefine(validateFirstXMitreAlias); * ``` */ export function createFirstXMitreAliasRefinement() { @@ -170,7 +170,7 @@ export function createCitationsRefinement() { * @example * ```typescript * const validateFirstBundleObject = createFirstBundleObjectRefinement(); - * const schema = extensibleStixBundleSchema.superRefine(validateFirstBundleObject); + * const schema = stixBundleSchema.superRefine(validateFirstBundleObject); * ``` */ export function createFirstBundleObjectRefinement() { diff --git a/src/schemas/sdo/analytic.schema.ts b/src/schemas/sdo/analytic.schema.ts index 82c39600..523437ca 100644 --- a/src/schemas/sdo/analytic.schema.ts +++ b/src/schemas/sdo/analytic.schema.ts @@ -1,9 +1,9 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixIdValidator } from '../common/stix-identifier.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { xMitreDomainsSchema, xMitrePlatformsSchema } from '../common/common-properties.js'; import { createAttackExternalReferencesSchema } from '../common/misc.js'; +import { createStixIdValidator } from '../common/stix-identifier.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -107,7 +107,7 @@ export type MutableElements = z.infer; // ///////////////////////////////////// -export const extensibleAnalyticSchema = attackBaseDomainObjectSchema +export const analyticSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-analytic'), @@ -134,6 +134,4 @@ export const extensibleAnalyticSchema = attackBaseDomainObjectSchema }) .strict(); -export const analyticSchema = extensibleAnalyticSchema; - -export type Analytic = z.infer; +export type Analytic = z.infer; diff --git a/src/schemas/sdo/asset.schema.ts b/src/schemas/sdo/asset.schema.ts index f7a7840a..57896a4c 100644 --- a/src/schemas/sdo/asset.schema.ts +++ b/src/schemas/sdo/asset.schema.ts @@ -1,14 +1,14 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, - descriptionSchema, - xMitrePlatformsSchema, - xMitreDomainsSchema, + createAttackExternalReferencesSchema, createStixIdValidator, + createStixTypeValidator, + descriptionSchema, xMitreContributorsSchema, + xMitreDomainsSchema, xMitreModifiedByRefSchema, - createAttackExternalReferencesSchema, - createStixTypeValidator, + xMitrePlatformsSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -78,7 +78,7 @@ export type RelatedAssets = z.infer; // ///////////////////////////////////// -export const extensibleAssetSchema = attackBaseDomainObjectSchema +export const assetSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-asset'), @@ -107,7 +107,4 @@ export const extensibleAssetSchema = attackBaseDomainObjectSchema }) .strict(); -// No refinements currently exist on assets, so just export an alias -export const assetSchema = extensibleAssetSchema; - -export type Asset = z.infer; +export type Asset = z.infer; diff --git a/src/schemas/sdo/campaign.schema.ts b/src/schemas/sdo/campaign.schema.ts index 928710b5..1a0051b2 100644 --- a/src/schemas/sdo/campaign.schema.ts +++ b/src/schemas/sdo/campaign.schema.ts @@ -1,17 +1,17 @@ +import { createCitationsRefinement, createFirstAliasRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; import { - stixTimestampSchema, - descriptionSchema, - xMitreDomainsSchema, - createStixIdValidator, aliasesSchema, createAttackExternalReferencesSchema, - xMitreModifiedByRefSchema, - xMitreContributorsSchema, + createStixIdValidator, createStixTypeValidator, + descriptionSchema, + stixTimestampSchema, + xMitreContributorsSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, } from '../common/index.js'; -import { createFirstAliasRefinement, createCitationsRefinement } from '@/refinements/index.js'; ///////////////////////////////////// // @@ -83,7 +83,7 @@ export type XMitreLastSeenCitation = z.infer { - createFirstAliasRefinement()(ctx); - createCitationsRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createCitationsRefinement()(ctx); + }); -// Define the type for Campaign -export type Campaign = z.infer; +export type Campaign = z.infer; diff --git a/src/schemas/sdo/collection.schema.ts b/src/schemas/sdo/collection.schema.ts index ad8dbe5e..d8fc6ccb 100644 --- a/src/schemas/sdo/collection.schema.ts +++ b/src/schemas/sdo/collection.schema.ts @@ -39,7 +39,7 @@ export type ObjectVersionReference = z.infer; +export type Collection = z.infer; diff --git a/src/schemas/sdo/data-component.schema.ts b/src/schemas/sdo/data-component.schema.ts index 997cf4da..1ab332bc 100644 --- a/src/schemas/sdo/data-component.schema.ts +++ b/src/schemas/sdo/data-component.schema.ts @@ -1,6 +1,5 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createStixIdValidator, descriptionSchema, @@ -9,6 +8,7 @@ import { xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -29,7 +29,7 @@ export type XMitreDataSourceRef = z.infer; // ///////////////////////////////////// -export const extensibleDataComponentSchema = attackBaseDomainObjectSchema +export const dataComponentSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-data-component'), @@ -51,7 +51,4 @@ export const extensibleDataComponentSchema = attackBaseDomainObjectSchema }) .strict(); -// No refinements currently exist on data components, so just export an alias -export const dataComponentSchema = extensibleDataComponentSchema; - -export type DataComponent = z.infer; +export type DataComponent = z.infer; diff --git a/src/schemas/sdo/data-source.schema.ts b/src/schemas/sdo/data-source.schema.ts index 543c8360..64f0474f 100644 --- a/src/schemas/sdo/data-source.schema.ts +++ b/src/schemas/sdo/data-source.schema.ts @@ -1,15 +1,15 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixIdValidator } from '../common/stix-identifier.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { - xMitrePlatformsSchema, - xMitreDomainsSchema, + createAttackExternalReferencesSchema, descriptionSchema, - xMitreModifiedByRefSchema, xMitreContributorsSchema, - createAttackExternalReferencesSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, + xMitrePlatformsSchema, } from '../common/index.js'; +import { createStixIdValidator } from '../common/stix-identifier.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -45,7 +45,7 @@ export type XMitreCollectionLayers = z.infer; +export type DataSource = z.infer; diff --git a/src/schemas/sdo/detection-strategy.schema.ts b/src/schemas/sdo/detection-strategy.schema.ts index 76238476..ecf534f9 100644 --- a/src/schemas/sdo/detection-strategy.schema.ts +++ b/src/schemas/sdo/detection-strategy.schema.ts @@ -1,13 +1,13 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixIdValidator } from '../common/stix-identifier.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { xMitreContributorsSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/common-properties.js'; import { createAttackExternalReferencesSchema } from '../common/misc.js'; +import { createStixIdValidator } from '../common/stix-identifier.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -15,7 +15,7 @@ import { createAttackExternalReferencesSchema } from '../common/misc.js'; // ///////////////////////////////////// -export const extensibleDetectionStrategySchema = attackBaseDomainObjectSchema +export const detectionStrategySchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-detection-strategy'), @@ -40,6 +40,4 @@ export const extensibleDetectionStrategySchema = attackBaseDomainObjectSchema 'The detection logic and patterns used to identify malicious activities based on the collected data.', }); -export const detectionStrategySchema = extensibleDetectionStrategySchema; - export type DetectionStrategy = z.infer; diff --git a/src/schemas/sdo/group.schema.ts b/src/schemas/sdo/group.schema.ts index af6914b9..4d6d04fb 100644 --- a/src/schemas/sdo/group.schema.ts +++ b/src/schemas/sdo/group.schema.ts @@ -1,11 +1,11 @@ -import { z } from 'zod/v4'; +import { createFirstAliasRefinement } from '@/refinements/index.js'; import { attackBaseDomainObjectSchema } from '@/schemas/common/attack-base-object.js'; import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; -import { createFirstAliasRefinement } from '@/refinements/index.js'; +import { z } from 'zod/v4'; import { aliasesSchema, - createStixIdValidator, createAttackExternalReferencesSchema, + createStixIdValidator, stixTimestampSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, @@ -13,7 +13,7 @@ import { import { AttackMotivationOV, AttackResourceLevelOV } from '../common/open-vocabulary.js'; // Group Schema -export const extensibleGroupSchema = attackBaseDomainObjectSchema +export const groupSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('intrusion-set'), @@ -68,12 +68,9 @@ export const extensibleGroupSchema = attackBaseDomainObjectSchema description: 'The secondary reasons, motivations, or purposes behind this Intrusion Set', }), }) - .strict(); - -export const groupSchema = extensibleGroupSchema.check((ctx) => { - // validate that when aliases are present, the first alias must match the object's name - createFirstAliasRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + }); -// Define the TypeScript type -export type Group = z.infer; +export type Group = z.infer; diff --git a/src/schemas/sdo/identity.schema.ts b/src/schemas/sdo/identity.schema.ts index 67d5da03..6cf499fd 100644 --- a/src/schemas/sdo/identity.schema.ts +++ b/src/schemas/sdo/identity.schema.ts @@ -1,9 +1,9 @@ -import { z } from 'zod/v4'; -import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; -import { createStixIdValidator } from '@/schemas/common/stix-identifier.js'; -import { objectMarkingRefsSchema } from '@/schemas/common/common-properties.js'; import { attackBaseDomainObjectSchema } from '@/schemas/common/attack-base-object.js'; +import { objectMarkingRefsSchema } from '@/schemas/common/common-properties.js'; import { IdentityClassOV, IndustrySectorOV } from '@/schemas/common/open-vocabulary.js'; +import { createStixIdValidator } from '@/schemas/common/stix-identifier.js'; +import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; +import { z } from 'zod/v4'; ///////////////////////////////////// // @@ -11,7 +11,7 @@ import { IdentityClassOV, IndustrySectorOV } from '@/schemas/common/open-vocabul // ///////////////////////////////////// -export const extensibleIdentitySchema = attackBaseDomainObjectSchema +export const identitySchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('identity'), @@ -66,8 +66,4 @@ export const extensibleIdentitySchema = attackBaseDomainObjectSchema }) .strict(); -// Alias because identities currently don't have any refinements -export const identitySchema = extensibleIdentitySchema; - -// Define the type for Identity -export type Identity = z.infer; +export type Identity = z.infer; diff --git a/src/schemas/sdo/index.ts b/src/schemas/sdo/index.ts index 9b86b617..3ba8d8ff 100644 --- a/src/schemas/sdo/index.ts +++ b/src/schemas/sdo/index.ts @@ -1,132 +1,114 @@ -export { analyticSchema, type Analytic, extensibleAnalyticSchema } from './analytic.schema.js'; +export { analyticSchema, type Analytic } from './analytic.schema.js'; export { - xMitreSectorsSchema, - type XMitreSectors, + assetSchema, relatedAssetSchema, relatedAssetsSchema, + xMitreSectorsSchema, + type Asset, type RelatedAsset, type RelatedAssets, - extensibleAssetSchema, - assetSchema, - type Asset, + type XMitreSectors, } from './asset.schema.js'; export { + campaignSchema, xMitreFirstSeenCitationSchema, xMitreLastSeenCitationSchema, + type Campaign, type XMitreFirstSeenCitation, type XMitreLastSeenCitation, - extensibleCampaignSchema, - campaignSchema, - type Campaign, } from './campaign.schema.js'; export { - objectVersionReferenceSchema, - type ObjectVersionReference, - extensibleCollectionSchema, collectionSchema, + objectVersionReferenceSchema, type Collection, + type ObjectVersionReference, } from './collection.schema.js'; export { - xMitreDataSourceRefSchema, - type XMitreDataSourceRef, - extensibleDataComponentSchema, dataComponentSchema, + xMitreDataSourceRefSchema, type DataComponent, + type XMitreDataSourceRef, } from './data-component.schema.js'; -export { - extensibleDetectionStrategySchema, - detectionStrategySchema, - type DetectionStrategy, -} from './detection-strategy.schema.js'; +export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js'; -export { extensibleGroupSchema, groupSchema, type Group } from './group.schema.js'; +export { groupSchema, type Group } from './group.schema.js'; -export { extensibleIdentitySchema, identitySchema, type Identity } from './identity.schema.js'; +export { identitySchema, type Identity } from './identity.schema.js'; export { - xMitreLogSourcePermutationsSchema, - type XMitreLogSourcePermutations, - extensibleLogSourceSchema, logSourceSchema, + xMitreLogSourcePermutationsSchema, type LogSource, + type XMitreLogSourcePermutations, } from './log-source.schema.js'; export { - xMitreCollectionLayersSchema, - type XMitreCollectionLayers, - extensibleDataSourceSchema, dataSourceSchema, + xMitreCollectionLayersSchema, type DataSource, + type XMitreCollectionLayers, } from './data-source.schema.js'; -export { extensibleMalwareSchema, malwareSchema, type Malware } from './malware.schema.js'; +export { malwareSchema, type Malware } from './malware.schema.js'; export { - xMitreTacticRefsSchema, - type XMitreTacticRefs, - extensibleMatrixSchema, matrixSchema, + xMitreTacticRefsSchema, type Matrix, + type XMitreTacticRefs, } from './matrix.schema.js'; -export { - extensibleMitigationSchema, - mitigationSchema, - type Mitigation, -} from './mitigation.schema.js'; +export { mitigationSchema, type Mitigation } from './mitigation.schema.js'; -export { extensibleSoftwareSchema, softwareSchema, type Software } from './software.schema.js'; +export { softwareSchema, type Software } from './software.schema.js'; export { - xMitreShortNameSchema, - type XMitreShortName, - extensibleTacticSchema, tacticSchema, + xMitreShortNameSchema, type Tactic, + type XMitreShortName, } from './tactic.schema.js'; export { - xMitreNetworkRequirementsSchema, - type XMitreNetworkRequirements, + techniqueSchema, + xMitreDataSourceSchema, + xMitreDataSourcesSchema, + xMitreDefenseBypassesSchema, + xMitreDetectionSchema, xMitreEffectivePermissionsSchema, - type XMitreEffectivePermissions, xMitreImpactTypeSchema, - type XMitreImpactType, - xMitreSystemRequirementsSchema, - type XMitreSystemRequirements, - xMitreRemoteSupportSchema, - type XMitreRemoteSupport, + xMitreIsSubtechniqueSchema, + xMitreNetworkRequirementsSchema, xMitrePermissionsRequiredSchema, - type XMitrePermissionsRequired, - xMitreDataSourceSchema, - xMitreDataSourcesSchema, + xMitreRemoteSupportSchema, + xMitreSystemRequirementsSchema, + xMitreTacticTypeSchema, + type Technique, type XMitreDataSource, type XMitreDataSources, - xMitreIsSubtechniqueSchema, - type XMitreIsSubtechnique, - xMitreTacticTypeSchema, - type XMitreTacticType, - xMitreDefenseBypassesSchema, type XMitreDefenseBypasses, - xMitreDetectionSchema, type XMitreDetection, - extensibleTechniqueSchema, - techniqueSchema, - type Technique, + type XMitreEffectivePermissions, + type XMitreImpactType, + type XMitreIsSubtechnique, + type XMitreNetworkRequirements, + type XMitrePermissionsRequired, + type XMitreRemoteSupport, + type XMitreSystemRequirements, + type XMitreTacticType, } from './technique.schema.js'; -export { extensibleToolSchema, toolSchema, type Tool } from './tool.schema.js'; +export { toolSchema, type Tool } from './tool.schema.js'; export { - type AttackObject, attackObjectsSchema, - type AttackObjects, - extensibleStixBundleSchema, stixBundleSchema, + type AttackObject, + type AttackObjects, type StixBundle, } from './stix-bundle.schema.js'; diff --git a/src/schemas/sdo/log-source.schema.ts b/src/schemas/sdo/log-source.schema.ts index 638cf106..cedc77b0 100644 --- a/src/schemas/sdo/log-source.schema.ts +++ b/src/schemas/sdo/log-source.schema.ts @@ -1,11 +1,11 @@ import { z } from 'zod/v4'; import { - xMitreDomainsSchema, - xMitreModifiedByRefSchema, attackBaseDomainObjectSchema, createAttackExternalReferencesSchema, createStixIdValidator, createStixTypeValidator, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -55,7 +55,7 @@ export type XMitreLogSourcePermutations = z.infer; +export type LogSource = z.infer; diff --git a/src/schemas/sdo/malware.schema.ts b/src/schemas/sdo/malware.schema.ts index 9111bda0..3fe7d662 100644 --- a/src/schemas/sdo/malware.schema.ts +++ b/src/schemas/sdo/malware.schema.ts @@ -1,6 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { createStixTypeValidator } from '../common/stix-type.js'; -import { softwareSchema } from './software.schema.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -9,15 +11,13 @@ import { stixTimestampSchema, } from '../common/index.js'; import { - MalwareCapabilityOV, - ProcessorArchitectureOV, ImplementationLanguageOV, + MalwareCapabilityOV, MalwareTypeOV, + ProcessorArchitectureOV, } from '../common/open-vocabulary.js'; -import { - createFirstAliasRefinement, - createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; +import { softwareSchema } from './software.schema.js'; ///////////////////////////////////// // @@ -39,7 +39,7 @@ export const stixArtifactType = createStixIdValidator('artifact').meta({ // ///////////////////////////////////// -export const extensibleMalwareSchema = softwareSchema +export const malwareSchema = softwareSchema .extend({ id: createStixIdValidator('malware'), @@ -120,17 +120,10 @@ export const extensibleMalwareSchema = softwareSchema 'The sample_refs property specifies a list of identifiers of the SCO file or artifact objects associated with this malware instance(s) or family.', }), }) - .strict(); - -// Create refinement instances -const validateFirstXMitreAlias = createFirstXMitreAliasRefinement(); -const validateFirstAlias = createFirstAliasRefinement(); - -// Apply a single refinement that combines both refinements -export const malwareSchema = extensibleMalwareSchema.check((ctx) => { - validateFirstAlias(ctx); - validateFirstXMitreAlias(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createFirstXMitreAliasRefinement()(ctx); + }); -// Define the type for Malware -export type Malware = z.infer; +export type Malware = z.infer; diff --git a/src/schemas/sdo/matrix.schema.ts b/src/schemas/sdo/matrix.schema.ts index fb57d475..25115532 100644 --- a/src/schemas/sdo/matrix.schema.ts +++ b/src/schemas/sdo/matrix.schema.ts @@ -1,12 +1,12 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createStixIdValidator, descriptionSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -28,7 +28,7 @@ export type XMitreTacticRefs = z.infer; // ///////////////////////////////////// -export const extensibleMatrixSchema = attackBaseDomainObjectSchema +export const matrixSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-matrix'), @@ -49,7 +49,4 @@ export const extensibleMatrixSchema = attackBaseDomainObjectSchema }) .strict(); -// Alias unless/until matrices require at least one refinement -export const matrixSchema = extensibleMatrixSchema; - -export type Matrix = z.infer; +export type Matrix = z.infer; diff --git a/src/schemas/sdo/mitigation.schema.ts b/src/schemas/sdo/mitigation.schema.ts index d4ea6c74..347d7852 100644 --- a/src/schemas/sdo/mitigation.schema.ts +++ b/src/schemas/sdo/mitigation.schema.ts @@ -1,6 +1,5 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -11,6 +10,7 @@ import { xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -18,7 +18,7 @@ import { // ///////////////////////////////////// -export const extensibleMitigationSchema = attackBaseDomainObjectSchema +export const mitigationSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('course-of-action'), @@ -47,8 +47,4 @@ export const extensibleMitigationSchema = attackBaseDomainObjectSchema }) .strict(); -// Alias unless/until mitigations require at least one refinement -export const mitigationSchema = extensibleMitigationSchema; - -// Define the type for Mitigation -export type Mitigation = z.infer; +export type Mitigation = z.infer; diff --git a/src/schemas/sdo/software.schema.ts b/src/schemas/sdo/software.schema.ts index e551f3b9..ccaa2d60 100644 --- a/src/schemas/sdo/software.schema.ts +++ b/src/schemas/sdo/software.schema.ts @@ -1,15 +1,15 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; import { + aliasesSchema, + createMultiStixTypeValidator, descriptionSchema, - xMitrePlatformsSchema, - stixCreatedByRefSchema, + externalReferencesSchema, objectMarkingRefsSchema, + stixCreatedByRefSchema, xMitreDomainsSchema, - aliasesSchema, xMitreModifiedByRefSchema, - externalReferencesSchema, - createMultiStixTypeValidator, + xMitrePlatformsSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -18,7 +18,7 @@ import { // ///////////////////////////////////// -export const extensibleSoftwareSchema = attackBaseDomainObjectSchema.extend({ +export const softwareSchema = attackBaseDomainObjectSchema.extend({ type: createMultiStixTypeValidator(['malware', 'tool']), created_by_ref: stixCreatedByRefSchema.meta({ @@ -52,8 +52,4 @@ export const extensibleSoftwareSchema = attackBaseDomainObjectSchema.extend({ .meta({ description: 'Alternative names used to identify this software.' }), }); -// Alias unless/until software requires at least one refinement -export const softwareSchema = extensibleSoftwareSchema; - -// Define the type for Software -export type Software = z.infer; +export type Software = z.infer; diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 0fe47694..6b264d68 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -1,29 +1,29 @@ +import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createStixIdValidator } from '../common/stix-identifier.js'; -import { type Malware, malwareSchema } from './malware.schema.js'; +import { type StixSpecVersion, stixSpecVersionSchema } from '../common/stix-spec-version.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; +import { + type MarkingDefinition, + markingDefinitionSchema, +} from '../smo/marking-definition.schema.js'; +import { type Relationship, relationshipSchema } from '../sro/relationship.schema.js'; +import { type Analytic, analyticSchema } from './analytic.schema.js'; import { type Asset, assetSchema } from './asset.schema.js'; import { type Campaign, campaignSchema } from './campaign.schema.js'; +import { type Collection, collectionSchema } from './collection.schema.js'; import { type DataComponent, dataComponentSchema } from './data-component.schema.js'; -import { type LogSource, logSourceSchema } from './log-source.schema.js'; import { type DataSource, dataSourceSchema } from './data-source.schema.js'; +import { type DetectionStrategy, detectionStrategySchema } from './detection-strategy.schema.js'; +import { type Group, groupSchema } from './group.schema.js'; import { type Identity, identitySchema } from './identity.schema.js'; +import { type LogSource, logSourceSchema } from './log-source.schema.js'; +import { type Malware, malwareSchema } from './malware.schema.js'; import { type Matrix, matrixSchema } from './matrix.schema.js'; -import { type Tool, toolSchema } from './tool.schema.js'; +import { type Mitigation, mitigationSchema } from './mitigation.schema.js'; import { type Tactic, tacticSchema } from './tactic.schema.js'; import { type Technique, techniqueSchema } from './technique.schema.js'; -import { type Group, groupSchema } from './group.schema.js'; -import { type Mitigation, mitigationSchema } from './mitigation.schema.js'; -import { type Collection, collectionSchema } from './collection.schema.js'; -import { type DetectionStrategy, detectionStrategySchema } from './detection-strategy.schema.js'; -import { type Analytic, analyticSchema } from './analytic.schema.js'; -import { type Relationship, relationshipSchema } from '../sro/relationship.schema.js'; -import { - type MarkingDefinition, - markingDefinitionSchema, -} from '../smo/marking-definition.schema.js'; -import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; -import { stixSpecVersionSchema, type StixSpecVersion } from '../common/stix-spec-version.js'; +import { type Tool, toolSchema } from './tool.schema.js'; export type AttackObject = | Malware @@ -176,7 +176,7 @@ export type AttackObjects = z.infer; // ///////////////////////////////////// -export const extensibleStixBundleSchema = z +export const stixBundleSchema = z .object({ id: createStixIdValidator('bundle'), @@ -188,15 +188,9 @@ export const extensibleStixBundleSchema = z objects: attackObjectsSchema, }) - .strict(); - -// Create refinement instance -const validateFirstBundleObject = createFirstBundleObjectRefinement(); - -// Apply the refinement -export const stixBundleSchema = extensibleStixBundleSchema.check((ctx) => { - validateFirstBundleObject(ctx); -}); + .strict() + .check((ctx) => { + createFirstBundleObjectRefinement()(ctx); + }); -// Define the type for StixBundle -export type StixBundle = z.infer; +export type StixBundle = z.infer; diff --git a/src/schemas/sdo/tactic.schema.ts b/src/schemas/sdo/tactic.schema.ts index 6fe81209..3dbe8e9e 100644 --- a/src/schemas/sdo/tactic.schema.ts +++ b/src/schemas/sdo/tactic.schema.ts @@ -1,13 +1,13 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, + createAttackExternalReferencesSchema, createStixIdValidator, createStixTypeValidator, descriptionSchema, - xMitreModifiedByRefSchema, - xMitreDomainsSchema, xMitreContributorsSchema, - createAttackExternalReferencesSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -72,7 +72,7 @@ export type XMitreShortName = z.infer; // ///////////////////////////////////// -export const extensibleTacticSchema = attackBaseDomainObjectSchema +export const tacticSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-tactic'), @@ -97,7 +97,4 @@ export const extensibleTacticSchema = attackBaseDomainObjectSchema }) .strict(); -// Alias unless/until tactics require at least one refinement -export const tacticSchema = extensibleTacticSchema; - -export type Tactic = z.infer; +export type Tactic = z.infer; diff --git a/src/schemas/sdo/technique.schema.ts b/src/schemas/sdo/technique.schema.ts index d2c3bffc..4309637a 100644 --- a/src/schemas/sdo/technique.schema.ts +++ b/src/schemas/sdo/technique.schema.ts @@ -1,21 +1,21 @@ +import { + createAttackIdInExternalReferencesRefinement, + createEnterpriseOnlyPropertiesRefinement, + createMobileOnlyPropertiesRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, - descriptionSchema, - xMitrePlatformsSchema, + createAttackExternalReferencesSchema, createStixIdValidator, createStixTypeValidator, - xMitreModifiedByRefSchema, - xMitreDomainsSchema, - xMitreContributorsSchema, + descriptionSchema, killChainPhaseSchema, - createAttackExternalReferencesSchema, + xMitreContributorsSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, + xMitrePlatformsSchema, } from '../common/index.js'; -import { - createAttackIdInExternalReferencesRefinement, - createEnterpriseOnlyPropertiesRefinement, - createMobileOnlyPropertiesRefinement, -} from '@/refinements/index.js'; ///////////////////////////////////// // @@ -325,7 +325,7 @@ export type XMitreDetection = z.infer; // ///////////////////////////////////// -export const extensibleTechniqueSchema = attackBaseDomainObjectSchema +export const techniqueSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('attack-pattern'), @@ -368,16 +368,11 @@ export const extensibleTechniqueSchema = attackBaseDomainObjectSchema x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional(), }) - .strict(); - -// Apply the refinements for techniques -export const techniqueSchema = extensibleTechniqueSchema.check((ctx) => { - // Validates that the first external reference is a valid ATT&CK ID - createAttackIdInExternalReferencesRefinement()(ctx); - // Validates that the technique only contains properties permissible by the target tactic in Enterprise - createEnterpriseOnlyPropertiesRefinement()(ctx); - // Validates that the technique only contains properties permissible in Mobile (if the technique belongs to Mobile) - createMobileOnlyPropertiesRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createAttackIdInExternalReferencesRefinement()(ctx); + createEnterpriseOnlyPropertiesRefinement()(ctx); + createMobileOnlyPropertiesRefinement()(ctx); + }); -export type Technique = z.infer; +export type Technique = z.infer; diff --git a/src/schemas/sdo/tool.schema.ts b/src/schemas/sdo/tool.schema.ts index 05569db8..c0e5c143 100644 --- a/src/schemas/sdo/tool.schema.ts +++ b/src/schemas/sdo/tool.schema.ts @@ -1,5 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { softwareSchema } from './software.schema.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -8,10 +11,7 @@ import { killChainPhaseSchema, } from '../common/index.js'; import { ToolTypeOV } from '../common/open-vocabulary.js'; -import { - createFirstAliasRefinement, - createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +import { softwareSchema } from './software.schema.js'; ///////////////////////////////////// // @@ -19,7 +19,7 @@ import { // ///////////////////////////////////// -export const extensibleToolSchema = softwareSchema +export const toolSchema = softwareSchema .extend({ id: createStixIdValidator('tool'), @@ -47,12 +47,10 @@ export const extensibleToolSchema = softwareSchema x_mitre_old_attack_id: createOldMitreAttackIdSchema('tool').optional(), }) - .strict(); - -// Apply a single refinement that combines both refinements -export const toolSchema = extensibleToolSchema.check((ctx) => { - createFirstXMitreAliasRefinement()(ctx); - createFirstAliasRefinement()(ctx); -}); -// Define the type for Tool -export type Tool = z.infer; + .strict() + .check((ctx) => { + createFirstXMitreAliasRefinement()(ctx); + createFirstAliasRefinement()(ctx); + }); + +export type Tool = z.infer; diff --git a/src/schemas/sro/relationship.schema.ts b/src/schemas/sro/relationship.schema.ts index 2b4af2de..012424d1 100644 --- a/src/schemas/sro/relationship.schema.ts +++ b/src/schemas/sro/relationship.schema.ts @@ -1,3 +1,4 @@ +import { createFoundInRelationshipRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseRelationshipObjectSchema, @@ -5,12 +6,11 @@ import { createStixTypeValidator, descriptionSchema, stixIdentifierSchema, - type StixIdentifier, - type StixType, stixTypeSchema, xMitreModifiedByRefSchema, + type StixIdentifier, + type StixType, } from '../common/index.js'; -import { createFoundInRelationshipRefinement } from '@/refinements/index.js'; ///////////////////////////////////// // @@ -285,7 +285,7 @@ export function createRelationshipValidationRefinement() { // ///////////////////////////////////// -export const extensibleRelationshipSchema = attackBaseRelationshipObjectSchema +export const relationshipSchema = attackBaseRelationshipObjectSchema .extend({ id: createStixIdValidator('relationship'), @@ -307,11 +307,10 @@ export const extensibleRelationshipSchema = attackBaseRelationshipObjectSchema name: true, x_mitre_version: true, }) - .strict(); - -export const relationshipSchema = extensibleRelationshipSchema.check((ctx) => { - createRelationshipValidationRefinement()(ctx); - createFoundInRelationshipRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createRelationshipValidationRefinement()(ctx); + createFoundInRelationshipRefinement()(ctx); + }); -export type Relationship = z.infer; +export type Relationship = z.infer; From d9528fafce971510b8abd841d5c7b9619b8a0ac1 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:57:56 -0500 Subject: [PATCH 02/39] docs: add additional documentation to docusaurus site --- .gitignore | 3 + README.md | 70 +- docusaurus/.gitignore | 3 - .../attack-specification-overview.md | 269 +++++++ docusaurus/docs/explanation/compatibility.md | 245 ++++++ .../docs/explanation/identifier-systems.md | 484 ++++++++++++ docusaurus/docs/explanation/index.md | 192 +++++ .../explanation/object-design-rationale.md | 457 ++++++++++++ docusaurus/docs/explanation/schema-design.md | 478 ++++++++++++ .../docs/explanation/stix-foundation.md | 454 +++++++++++ docusaurus/docs/explanation/trade-offs.md | 220 ++++++ .../docs/explanation/versioning-philosophy.md | 517 +++++++++++++ docusaurus/docs/explanation/why-adm-exists.md | 298 ++++++++ .../docs/how-to-guides/error-handling.md | 704 ++++++++++++++++++ .../docs/how-to-guides/extend-schemas.md | 441 +++++++++++ docusaurus/docs/how-to-guides/index.md | 48 ++ .../docs/how-to-guides/manage-data-sources.md | 403 ++++++++++ docusaurus/docs/how-to-guides/performance.md | 534 +++++++++++++ .../docs/how-to-guides/validate-bundles.md | 413 ++++++++++ docusaurus/docs/index.md | 75 ++ docusaurus/docs/overview.md | 238 +++++- .../docs/reference/api/attack-data-model.md | 306 ++++++++ docusaurus/docs/reference/api/data-sources.md | 427 +++++++++++ docusaurus/docs/reference/api/index.md | 172 +++++ docusaurus/docs/reference/api/utilities.md | 600 +++++++++++++++ docusaurus/docs/reference/configuration.md | 526 +++++++++++++ docusaurus/docs/reference/errors.md | 389 ++++++++++ docusaurus/docs/reference/index.md | 190 +++++ docusaurus/docs/reference/schemas/.gitkeep | 0 docusaurus/docs/sdo/stix-bundle.schema.md | 2 +- docusaurus/docs/tutorials/index.md | 52 ++ .../docs/tutorials/multi-domain-analysis.md | 282 +++++++ docusaurus/docs/tutorials/relationships.md | 431 +++++++++++ .../docs/tutorials/technique-browser.md | 448 +++++++++++ docusaurus/docs/tutorials/your-first-query.md | 204 +++++ docusaurus/docusaurus.config.ts | 87 ++- docusaurus/sidebars.ts | 51 +- .../src/components/DocTypeIndicator/index.tsx | 133 ++++ .../DocTypeIndicator/styles.module.css | 214 ++++++ .../src/components/HomepageFeatures/index.tsx | 180 +++-- .../HomepageFeatures/styles.module.css | 227 +++++- docusaurus/src/css/custom.css | 288 +++++++ generate-docs.sh | 14 +- 43 files changed, 11638 insertions(+), 131 deletions(-) create mode 100644 docusaurus/docs/explanation/attack-specification-overview.md create mode 100644 docusaurus/docs/explanation/compatibility.md create mode 100644 docusaurus/docs/explanation/identifier-systems.md create mode 100644 docusaurus/docs/explanation/index.md create mode 100644 docusaurus/docs/explanation/object-design-rationale.md create mode 100644 docusaurus/docs/explanation/schema-design.md create mode 100644 docusaurus/docs/explanation/stix-foundation.md create mode 100644 docusaurus/docs/explanation/trade-offs.md create mode 100644 docusaurus/docs/explanation/versioning-philosophy.md create mode 100644 docusaurus/docs/explanation/why-adm-exists.md create mode 100644 docusaurus/docs/how-to-guides/error-handling.md create mode 100644 docusaurus/docs/how-to-guides/extend-schemas.md create mode 100644 docusaurus/docs/how-to-guides/index.md create mode 100644 docusaurus/docs/how-to-guides/manage-data-sources.md create mode 100644 docusaurus/docs/how-to-guides/performance.md create mode 100644 docusaurus/docs/how-to-guides/validate-bundles.md create mode 100644 docusaurus/docs/index.md create mode 100644 docusaurus/docs/reference/api/attack-data-model.md create mode 100644 docusaurus/docs/reference/api/data-sources.md create mode 100644 docusaurus/docs/reference/api/index.md create mode 100644 docusaurus/docs/reference/api/utilities.md create mode 100644 docusaurus/docs/reference/configuration.md create mode 100644 docusaurus/docs/reference/errors.md create mode 100644 docusaurus/docs/reference/index.md create mode 100644 docusaurus/docs/reference/schemas/.gitkeep create mode 100644 docusaurus/docs/tutorials/index.md create mode 100644 docusaurus/docs/tutorials/multi-domain-analysis.md create mode 100644 docusaurus/docs/tutorials/relationships.md create mode 100644 docusaurus/docs/tutorials/technique-browser.md create mode 100644 docusaurus/docs/tutorials/your-first-query.md create mode 100644 docusaurus/src/components/DocTypeIndicator/index.tsx create mode 100644 docusaurus/src/components/DocTypeIndicator/styles.module.css diff --git a/.gitignore b/.gitignore index c880ebae..183ec516 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# auto-generated schema documentation +docusaurus/docs/reference/schemas/ + # TypeScript artifacts *.d.ts *.ts.map diff --git a/README.md b/README.md index ce720b8a..497f65f3 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,49 @@ # MITRE ATT&CK® Data Model -The ATT&CK Data Model (ADM) is a TypeScript library that provides a structured way to interact with MITRE ATT&CK datasets. It uses Zod schemas, TypeScript types, and ES6 classes to create a type-safe, object-oriented interface for navigating the ATT&CK data model. This library is designed to parse, validate, and serialize STIX 2.1 formatted content, making it easy to work with ATT&CK-related data in a programmatic and intuitive way. +**A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1** -You can browse the ATT&CK schemas in a user-friendly interface at: +The ATT&CK Data Model (ADM) provides a type-safe, object-oriented interface for working with MITRE ATT&CK datasets. +Built on STIX 2.1 compliance, it uses Zod schemas and TypeScript types to ensure data integrity while providing intuitive relationship navigation between ATT&CK objects. -https://mitre-attack.github.io/attack-data-model/. +## Key Features -This site is dynamically generated from the contents of the `@latest` distribution channel / `main` branch. Please note that we do not currently maintain separate documentation for previous releases. +- **Type-Safe**: Full TypeScript support with compile-time validation +- **STIX 2.1 Compliant**: Standards-compliant +- **Relationship Navigation**: Intuitive methods for exploring connections +- **Multiple Data Sources**: Official repository, local files, URLs, TAXII -## Features - -- **Type-Safe Data Parsing**: ADM validates STIX 2.1 bundles using Zod schemas, ensuring data model compliance and type safety. -- **Easy Relationship Navigation**: Each object instance contains pointers to related objects, simplifying the process of navigating between techniques, tactics, and other ATT&CK elements. -- **Supports Multiple Data Sources**: Load ATT&CK datasets from different sources, including GitHub, local files, URLs, and TAXII 2.1 servers (more data sources in development). -- Parsing, validation, and serialization of ATT&CK data -- ES6 classes for object-oriented data manipulation - -## Supported Data Sources - -- **`attack`**: Load ATT&CK data from the official MITRE ATT&CK STIX 2.1 GitHub repository. This serves as the source of truth for MITRE ATT&CK content. -- **`file`**: Load ATT&CK data from a local JSON file containing a STIX 2.1 bundle. -- **`url`**: Load ATT&CK data from a URL endpoint serving STIX 2.1 content. -- **`taxii`**: (Coming soon) Load ATT&CK data from a TAXII 2.1 server. - -## Installation - -To install from the npm registry, simply run: +## Quick Start ```bash npm install @mitre-attack/attack-data-model ``` -## ATT&CK Specification - -The ADM is built upon the MITRE ATT&CK® Specification, which formally defines the structure, properties, and relationships of ATT&CK objects. The ATT&CK Specification serves as the authoritative source for how ATT&CK data should be represented and interacted with. - -The ADM provides a codified expression of the ATT&CK Specification using Zod schemas and TypeScript types. By implementing the specification in code, the ADM ensures that all data parsed and manipulated through the library adheres to the defined standards of the ATT&CK data model. This includes strict validation of object structures, types, and required properties, providing developers with confidence in the integrity and consistency of the data they work with. - -While the ATT&CK Specification defines the data model itself, the ADM includes additional software engineering elements such as utility functions, classes, and methods to facilitate easier interaction with ATT&CK data. As such, the ADM is subject to its own development lifecycle and versioning, independent of the ATT&CK Specification. +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; -The version of the ATT&CK Specification that the ADM is based on is indicated in the [ATTACK_SPEC_VERSION](./ATTACK_SPEC_VERSION) file located in the repository. This file contains a single line specifying the semantic version of the ATT&CK Specification that the current ADM release is pinned to. +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); -It's important to note that the ADM's versioning may not directly align with the versioning of the ATT&CK Specification. The ADM follows its own semantic versioning release cadence to accommodate ongoing software engineering changes, enhancements, and fixes that may occur more frequently than updates to the ATT&CK Specification itself. +const uuid = await registerDataSource(dataSource); +const attackDataModel = loadDataModel(uuid); -By maintaining separate versioning, the ADM can evolve as a software library while remaining aligned with the underlying ATT&CK data model defined by the specification. This approach ensures that developers have access to the latest features and improvements in the ADM without being constrained by the update schedule of the ATT&CK Specification. +// Navigate relationships intuitively +const technique = attackDataModel.techniques[0]; +const tactics = technique.getTactics(); +const mitigations = technique.getMitigations(); +``` ## Documentation -For detailed API documentation and usage examples, please refer to the [ATT&CK Data Model TypeScript API Documentation](docs/USAGE.md). +For detailed API documentation and usage examples, please refer to the [documentation](https://mitre-attack.github.io/attack-data-model/) + +- [Tutorials](https://mitre-attack.github.io/attack-data-model/tutorials/) - Learn by doing with step-by-step guides +- [How-to Guides](https://mitre-attack.github.io/attack-data-model/how-to-guides/) - Solve specific problems quickly +- [Reference](https://mitre-attack.github.io/attack-data-model/reference/) - Complete API and configuration documentation +- [Explanation](https://mitre-attack.github.io/attack-data-model/explanation/) - Understand design decisions and architecture ## Basic Usage @@ -59,7 +55,7 @@ Here's an example script that demonstrates how to use the ADM library to load AT import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; (async () => { - + // Instantiating a DataSource object will validate that the data source is accessible and readable const dataSource = new DataSource({ source: 'attack', // Built-in index to retrieve ATT&CK content from the official MITRE ATT&CK STIX 2.1 GitHub repository @@ -83,7 +79,7 @@ import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/att // Type hinting is supported for all object properties if (technique.x_mitre_is_subtechnique) { - + // Access related objects with helpful getter methods console.log(technique.getParentTechnique()); } @@ -154,7 +150,7 @@ For more detailed examples, please refer to the [examples](./examples/README.md) ## Compatibility Matrix -Our [COMPATIBILITY.md](./docs/COMPATIBILITY.md) document tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK® dataset (`mitre-attack/attack-stix-data`). +Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/explanation/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK® dataset (`mitre-attack/attack-stix-data`). ## Contributing @@ -164,7 +160,7 @@ We welcome contributions! Please see our [CONTRIBUTING.md](./docs/CONTRIBUTING.m This project is licensed under the Apache 2.0 License. -## Notice +## Notice Copyright 2020-2025 The MITRE Corporation. diff --git a/docusaurus/.gitignore b/docusaurus/.gitignore index 885b00a7..b2d6de30 100644 --- a/docusaurus/.gitignore +++ b/docusaurus/.gitignore @@ -18,6 +18,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - -docs/*/* -!docs/sdo/stix-bundle.schema.md \ No newline at end of file diff --git a/docusaurus/docs/explanation/attack-specification-overview.md b/docusaurus/docs/explanation/attack-specification-overview.md new file mode 100644 index 00000000..2485133c --- /dev/null +++ b/docusaurus/docs/explanation/attack-specification-overview.md @@ -0,0 +1,269 @@ +# ATT&CK Specification Overview + +**Understanding the structure and purpose of the ATT&CK specification** + +The ATT&CK specification defines the formal structure, semantics, and constraints that govern how MITRE ATT&CK® data is represented, validated, and consumed. Understanding the specification's design philosophy and architecture is crucial for working effectively with ATT&CK data and building robust applications that leverage the ATT&CK framework. + +## What is the ATT&CK Specification? + +The ATT&CK specification is a codified expression of the concepts outlined in the [MITRE ATT&CK Philosophy Paper](https://attack.mitre.org/docs/ATTACK_Design_and_Philosophy_March_2020.pdf), which is in turn built atop the [STIX 2.1 specification](https://oasis-open.github.io/cti-documentation/resources#stix-21-specification). Rather than creating a completely new data format, the specification extends STIX 2.1 with ATT&CK-specific object types, properties, and validation rules. + +### The Specification Hierarchy + +``` +STIX 2.1 Specification (OASIS Standard) + │ + ├── Base STIX objects and properties + ├── Standard relationship patterns + ├── Extension mechanisms + │ + └── ATT&CK Specification Extensions + │ + ├── Custom object types (x-mitre-*) + ├── Custom properties (x_mitre_*) + ├── Custom relationship types + └── ATT&CK business rules and validation +``` + +**Key insight**: The ATT&CK specification is not a replacement for STIX—it's a disciplined extension that maintains full STIX compliance while adding the semantic richness needed to represent adversary tactics, techniques, and procedures. + +## Design Philosophy + +### Standards-First Approach + +The specification prioritizes standards compliance over custom optimization. +This design choice reflects several strategic decisions: + +#### Interoperability Over Convenience + +By extending rather than replacing STIX, ATT&CK data can be processed by existing STIX-compliant tools and workflows. + +#### Long-term Sustainability Over Short-term Simplicity + +Standards provide governance, stability, and community support that proprietary formats cannot match. The specification accepts some complexity in exchange for long-term viability. + +#### Community Ecosystem Over Isolated Solutions + +STIX compliance enables ATT&CK data to integrate naturally with threat intelligence platforms, security orchestration systems, and analyst tools that already support STIX. + +### Semantic Richness Through Extensions + +ATT&CK concepts require more specificity than generic STIX objects provide. The specification achieves this through disciplined extensions: + +#### Custom Object Types + +Objects like `x-mitre-tactic`, `x-mitre-matrix`, and `x-mitre-data-source` represent ATT&CK concepts that don't map cleanly to existing STIX types. + +#### Custom Properties + +Properties like `x_mitre_platforms`, `x_mitre_is_subtechnique`, and `x_mitre_attack_spec_version` add domain-specific metadata to standard STIX objects. + +#### Custom Relationships + +Relationship types like `subtechnique-of`, `detects`, and `mitigates` capture ATT&CK-specific associations between objects. + +## Architectural Implications + +### Object Identity Strategy + +The specification employs a dual-identity approach that balances STIX requirements with ATT&CK usability: + +#### Primary Identifiers (STIX IDs) + +Every object has a globally unique STIX ID (e.g., `attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298`) that ensures referential integrity across systems. + +#### Human-Readable Identifiers (ATT&CK IDs) + +Most objects also have human-readable ATT&CK IDs (e.g., `T1055`) stored as external references for documentation and communication. + +**Design rationale**: This dual approach supports both machine processing (via STIX IDs) and human comprehension (via ATT&CK IDs) without compromising either use case. + +### Relationship Architecture + +The specification models relationships as explicit STIX relationship objects rather than embedded references: + +```json +// ❌ Embedded reference approach +{ + "type": "attack-pattern", + "tactics": ["TA0001", "TA0002"] +} + +// ✅ STIX relationship approach +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "attack-pattern--12345...", + "target_ref": "x-mitre-tactic--67890..." +} +``` + +**Benefits**: + +- Relationships can carry their own metadata (descriptions, relationship type) +- Bidirectional navigation is naturally supported +- Relationship evolution doesn't require object schema changes + +### Extension Mechanisms + +The specification defines three primary extension patterns: + +#### 1. Custom Object Types + +```json +{ + "type": "x-mitre-tactic", + "id": "x-mitre-tactic--78b23412-0651-46d7-a540-170a1ce8bd5a", + "x_mitre_shortname": "execution" +} +``` + +Custom types follow the STIX Domain Object pattern but represent concepts unique to ATT&CK. + +#### 2. Custom Properties on Standard Objects + +```json +{ + "type": "attack-pattern", + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "x_mitre_platforms": ["Windows", "Linux"], + "x_mitre_is_subtechnique": false +} +``` + +Standard STIX objects are extended with ATT&CK-specific properties using the `x_mitre_` namespace. + +#### 3. Custom Relationship Types + +```json +{ + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern-- ", + "target_ref": "attack-pattern-- " +} +``` + +New relationship types capture ATT&CK-specific associations not covered by standard STIX relationships. + +## Validation Philosophy + +### Layered Validation Strategy + +The specification implements validation through multiple layers: + +#### 1. STIX Base Compliance + +All objects must conform to STIX 2.1 structural requirements (required fields, data types, etc.). + +#### 2. ATT&CK Extension Validation + +Custom properties and objects must conform to ATT&CK-specific schemas and constraints. + +#### 3. ATT&CK Business Rules + +Domain-specific rules enforce requirements like ATT&CK ID formats, tactic associations, and relationship constraints. + +#### 4. Cross-Object Validation + +References between objects must be valid and consistent (e.g., subtechnique parents must exist). + +### Strict vs. Relaxed Modes + +The specification supports two validation philosophies: + +#### Strict Mode + +**Philosophy**: "Data integrity is paramount—invalid data must be rejected completely" + +- All objects must pass all validation layers +- Processing aborts on any validation failure +- Appropriate for production systems requiring data quality guarantees + +#### Relaxed Mode + +**Philosophy**: "Partial data is better than no data—log errors but continue processing" + +- Invalid objects are logged but not rejected +- Processing continues with valid objects only +- Appropriate for research environments and data migration scenarios + +## Evolution Patterns + +### Version Management Strategy + +The specification uses three distinct versioning dimensions: + +#### STIX Version (`spec_version`) + +Tracks compliance with STIX specification versions (managed by OASIS). + +#### ATT&CK Specification Version (`x_mitre_attack_spec_version`) + +Tracks compatibility with ATT&CK specification extensions (managed by MITRE). + +#### Object Version (`x_mitre_version`) + +Tracks semantic changes to individual object content (managed by MITRE). + +**Design rationale**: This multi-dimensional approach allows independent evolution of STIX standards, ATT&CK specification features, and individual object content. + +### Backward Compatibility Strategy + +The specification maintains backward compatibility through: + +#### Additive Changes + +New fields and object types are added without removing existing ones. + +#### Deprecation Warnings + +Obsolete features are marked deprecated before removal, providing migration time. + +#### Version-Aware Processing + +Consuming applications can adapt behavior based on specification version. + +#### Legacy Support + +Deprecated features remain functional until formal removal in major version updates. + +## Working with the Specification + +### Understanding Specification Documents + +The specification exists in multiple forms: + +#### Formal Schema Definitions + +Zod schemas in the ATT&CK Data Model library provide executable validation rules. + +#### Reference Documentation + +Auto-generated schema documentation describes object structures and constraints. + +#### Philosophy Documents + +MITRE ATT&CK papers explain the conceptual foundations behind specification decisions. + +#### Implementation Examples + +Working code examples demonstrate specification usage patterns. + +### Common Specification Misconceptions + +#### "ATT&CK is just JSON files" + +**Reality**: ATT&CK is a structured specification with validation rules, semantic constraints, and evolution patterns. + +#### "Custom properties break STIX compliance" + +**Reality**: ATT&CK uses STIX-defined extension mechanisms to maintain full standards compliance. We currently use the STIX 2.0 compliant method of extending STIX, but plan to fully use STIX 2.1's extension definitions in the future. + +#### "ATT&CK IDs are primary identifiers" + +**Reality**: STIX IDs are primary identifiers; ATT&CK IDs are human-readable aliases stored as external references. + +#### "The specification is static" + +**Reality**: The specification evolves continuously with new object types, properties, and validation rules. diff --git a/docusaurus/docs/explanation/compatibility.md b/docusaurus/docs/explanation/compatibility.md new file mode 100644 index 00000000..1d1b6a1f --- /dev/null +++ b/docusaurus/docs/explanation/compatibility.md @@ -0,0 +1,245 @@ +# Compatibility + +**Understanding version relationships and compatibility across the ATT&CK ecosystem** + +The ATT&CK Data Model operates within a complex ecosystem of interconnected version dependencies. Understanding these relationships helps you make informed decisions about version selection, upgrade timing, and compatibility management for your applications. + +## The Compatibility Challenge + +ATT&CK's ecosystem involves multiple independent components that evolve at different rates: + +- **ATT&CK Data Model Library** (`@mitre-attack/attack-data-model`) - This TypeScript library +- **ATT&CK Specification** - Defines object schemas and validation rules +- **STIX Specification** - Core STIX standard that ATT&CK extends +- **ATT&CK Dataset Releases** - The actual threat intelligence data from MITRE + +Each component has its own versioning scheme and release cycle, creating a compatibility matrix that must be carefully managed. + +## Supported Versions Compatibility Matrix + +| ADM Version | ATT&CK Specification | STIX Version | Supported ATT&CK Releases | +|-------------|---------------------|--------------|---------------------------| +| 1.x, 2.x, 3.x | 3.2.0 | 2.1 | ≥15.x, ≤17.x | +| 4.x | 3.3.0 | 2.1 | ≥15.x, ≤18.x | +| 5.x *(future)* | 4.0.0 | 2.1 | ≥18.x | + +*Note: Other versions may work but are not officially supported or tested.* + +## Understanding Version Dependencies + +### ATT&CK Specification Evolution + +**Version 3.3.0 introduced significant changes:** + +- **New Object Types**: Detection Strategies, Analytics, and Log Sources +- **Deprecations**: Legacy Data Source detection relationships +- **Enhanced Features**: Campaign temporal tracking and asset relationship modeling + +**Version 4.0.0 (planned)** will include breaking changes: + +- Removal of deprecated data source `detects` relationships +- Potential schema changes affecting validation logic + +### Compatibility Implications + +#### Using Older ATT&CK Releases + +- May lack properties or objects expected by newer ADM versions +- Can cause validation errors or incomplete data mapping +- Generally safe for read-only analytical workflows + +#### Using Newer ATT&CK Releases + +- May introduce objects or properties not recognized by older ADM versions +- Risk of parsing failures or missing data +- Requires ADM updates for full feature support + +#### Specification Mismatches + +- Different specification versions may have incompatible validation rules +- Object schemas may differ, affecting data integrity +- Relationship types and constraints may change + +## Practical Compatibility Strategies + +### Development Environment Management + +```typescript +// Check compatibility at runtime +function validateCompatibility(attackDataModel: AttackDataModel) { + const specVersion = attackDataModel.getSpecificationVersion(); + const supportedVersions = ['3.2.0', '3.3.0']; + + if (!supportedVersions.includes(specVersion)) { + console.warn(`Specification version ${specVersion} may not be fully supported`); + } +} +``` + +### Version Pinning for Stability + +```typescript +// Pin specific versions for predictable behavior +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin to tested version + parsingMode: 'strict' +}); +``` + +### Graceful Degradation + +```typescript +// Handle version differences gracefully +function getDetectionData(technique: Technique) { + // Try modern detection strategy approach (3.3.0+) + if (technique.getDetectionStrategies) { + const strategies = technique.getDetectionStrategies(); + if (strategies.length > 0) { + return strategies; + } + } + + // Fall back to legacy data component approach + if (technique.getDetectedBy) { + return technique.getDetectedBy(); + } + + // Final fallback to detection text + return [{ description: technique.x_mitre_detection || 'No detection information available' }]; +} +``` + +## Version Migration Planning + +### Planning Specification Upgrades + +1. **Assessment Phase** + - Review changelog for breaking changes + - Identify affected code paths in your application + - Plan testing strategy for new features + +2. **Testing Phase** + - Test with representative data samples + - Validate existing functionality continues working + - Verify new features work as expected + +3. **Rollout Phase** + - Implement gradual rollout with monitoring + - Maintain rollback capability + - Update documentation and training materials + +### Handling Breaking Changes + +Breaking changes typically involve: + +- **Object Schema Changes**: New required fields or validation rules +- **Relationship Changes**: Modified relationship types or constraints +- **Deprecated Features**: Removal of legacy object types or properties + +**Migration Strategy**: + +```typescript +class VersionAwareProcessor { + processObjects(objects: AttackObject[]) { + return objects.map(obj => { + switch (this.getSpecVersion(obj)) { + case '3.2.0': + return this.processLegacyObject(obj); + case '3.3.0': + return this.processCurrentObject(obj); + default: + return this.processWithFallback(obj); + } + }); + } +} +``` + +## Compatibility Best Practices + +### For Application Developers + +1. **Version Awareness**: Always check specification versions before using new features +2. **Feature Detection**: Use capability detection over version checking when possible +3. **Flexible Parsing**: Use `relaxed` mode in production to handle data variations +4. **Comprehensive Testing**: Test with multiple ATT&CK dataset versions +5. **Documentation**: Document version requirements clearly for users + +### For Data Consumers + +1. **Stay Current**: Regularly update to supported ADM versions +2. **Monitor Changes**: Subscribe to release notes for breaking change notifications +3. **Test Early**: Test with pre-release versions when available +4. **Validate Data**: Implement data quality checks for version compatibility +5. **Plan Migrations**: Schedule regular upgrade cycles aligned with ATT&CK releases + +### For Integration Teams + +1. **Environment Isolation**: Use different versions in development vs production +2. **Automated Testing**: Include compatibility tests in CI/CD pipelines +3. **Monitoring**: Track compatibility issues in production deployments +4. **Rollback Plans**: Maintain ability to downgrade if issues arise +5. **Team Communication**: Share compatibility requirements across teams + +## Common Compatibility Issues + +### Schema Validation Failures + +**Problem**: Objects fail validation with newer specification versions + +**Solution**: + +- Update to compatible ADM version +- Use `relaxed` parsing mode temporarily +- Review and update custom validation logic + +### Missing Object Properties + +**Problem**: Expected properties don't exist in older datasets + +**Solution**: + +```typescript +// Defensive property access +const detectsBy = technique.x_mitre_data_sources || + technique.getDataComponents?.() || + []; +``` + +### Deprecated Feature Usage + +**Problem**: Application uses deprecated object types or relationships + +**Solution**: + +- Migrate to replacement features +- Implement feature detection +- Plan phased migration strategy + +## Future Compatibility Planning + +### Anticipated Changes + +- **STIX 2.2 Adoption**: May require major ADM version update +- **ATT&CK Specification 4.0**: Will remove legacy detection relationships +- **New Object Types**: Regular introduction of new ATT&CK object types +- **Performance Improvements**: Schema optimizations may affect validation + +### Staying Prepared + +1. **Follow Development**: Monitor ADM and ATT&CK development channels +2. **Participate in Community**: Engage with other users facing similar challenges +3. **Contribute Back**: Share compatibility issues and solutions with the community +4. **Plan Resources**: Budget time and resources for regular compatibility updates + +--- + +Understanding compatibility relationships helps you build robust applications that can evolve alongside the ATT&CK ecosystem. By planning for version changes and implementing defensive coding practices, you can maintain stable functionality while taking advantage of new capabilities as they become available. + +**Related Topics**: + +- [Versioning Philosophy](./versioning-philosophy) - Deep dive into ATT&CK's multi-dimensional versioning +- [Configuration Reference](../reference/configuration) - All supported configuration options +- [Error Handling Guide](../how-to-guides/error-handling) - Handle compatibility and parsing errors diff --git a/docusaurus/docs/explanation/identifier-systems.md b/docusaurus/docs/explanation/identifier-systems.md new file mode 100644 index 00000000..20e0ab6e --- /dev/null +++ b/docusaurus/docs/explanation/identifier-systems.md @@ -0,0 +1,484 @@ +# Identifier Systems + +**Understanding ATT&CK's multiple identification schemes and their purposes** + +ATT&CK employs three distinct identifier systems, each serving different purposes and use cases. This multi-layered approach often confuses newcomers who expect a single, simple identifier scheme. Understanding why multiple identifiers exist, how they interact, and when to use each type is crucial for effective ATT&CK data processing and integration. + +## The Identification Challenge + +### Competing Requirements + +ATT&CK must serve multiple audiences with conflicting identification needs: + +#### Human Communication + +Security analysts need memorable, meaningful identifiers for documentation, reports, and discussions: + +- "We observed T1055 (Process Injection) being used by APT1" +- "Our detection rules cover techniques T1055.001 through T1055.012" + +#### Machine Processing + +Applications need globally unique, collision-resistant identifiers for reliable data processing: + +- Database primary keys and foreign key relationships +- Cross-system references and data synchronization +- Programmatic lookups and relationship traversal + +#### Standards Compliance + +STIX 2.1 requires specific identifier formats and uniqueness guarantees: + +- UUID-based identifiers for global uniqueness +- Namespace prefixes for object type identification +- Referential integrity across STIX bundles + +### The Impossibility of One Perfect Identifier + +No single identifier scheme can satisfy all these requirements simultaneously: + +- **Human-readable IDs** (like "T1055") are meaningful but not globally unique +- **Globally unique IDs** (like UUIDs) are collision-resistant but not memorable +- **Sequential integers** are simple but create coordination problems across systems +- **Hash-based IDs** are deterministic but not human-interpretable + +**Solution**: Use multiple complementary identifier systems, each optimized for specific use cases. + +## The Three Identifier Systems + +### 1. STIX IDs: The Primary Identity System + +**Format**: `{object-type}--{uuid}` +**Example**: `attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298` + +#### Purpose and Characteristics + +**Primary identifiers**: Every ATT&CK object has exactly one STIX ID that serves as its canonical identity. + +**Global uniqueness**: UUIDs ensure no identifier collisions across all ATT&CK datasets, organizations, and time periods. + +**Standards compliance**: Required by STIX 2.1 specification for referential integrity and cross-system compatibility. + +**Referential integrity**: All object references in relationships use STIX IDs: + +```json +{ + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern--sub-technique-stix-id", + "target_ref": "attack-pattern--parent-technique-stix-id" +} +``` + +#### When to Use STIX IDs + +**✅ Recommended for**: + +- Programmatic object lookups and storage +- Database primary keys and foreign keys +- API endpoints and data synchronization +- Internal application logic and caching + +**❌ Not recommended for**: + +- User interfaces and human communication +- Documentation and report writing +- Manual analysis and investigation workflows + +#### Working with STIX IDs + +```typescript +// ✅ Correct usage - programmatic lookup +const technique = attackDataModel.getTechniqueById( + "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" +); + +// ❌ Avoid - hard to read and maintain +const displayName = "Technique attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298"; +``` + +### 2. ATT&CK IDs: The Human-Readable System + +**Format**: Varies by object type +**Examples**: `T1055`, `G0006`, `M1038`, `TA0002` + +#### Format Patterns by Object Type + +| Object Type | Format | Example | Description | +|-------------|--------|---------|-------------| +| Technique | `Txxxx` | `T1055` | Four-digit numeric sequence | +| Sub-technique | `Txxxx.yyy` | `T1055.001` | Parent technique + sub-technique suffix | +| Tactic | `TAxxxx` | `TA0002` | "TA" prefix + four-digit number | +| Group | `Gxxxx` | `G0006` | "G" prefix + four-digit number | +| Software | `Sxxxx` | `S0154` | "S" prefix + four-digit number | +| Mitigation | `Mxxxx` | `M1038` | "M" prefix + four-digit number | +| Data Source | `DSxxxx` | `DS0017` | "DS" prefix + four-digit number | +| Data Component | `DCxxxx` | `DC0024` | "DC" prefix + four-digit number | + +#### Storage as External References + +ATT&CK IDs are stored in the first external reference, not as primary properties: + +```json +{ + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1055", + "url": "https://attack.mitre.org/techniques/T1055" + } + ] +} +``` + +**Design rationale**: This approach maintains STIX compliance while preserving human-readable identifiers as metadata. + +#### Uniqueness Limitations + +**Important**: ATT&CK IDs are not guaranteed to be globally unique. + +**Historical collisions**: Legacy mitigations (pre-v5, July 2019) may share ATT&CK IDs with techniques due to deprecated 1:1 relationships. + +**Matrix sharing**: Matrices within the same domain use identical ATT&CK IDs (e.g., both Enterprise matrices use "enterprise-attack"). + +**Filtering strategy**: + +```typescript +// Filter out deprecated objects to avoid ID collisions +const currentTechniques = techniques.filter(t => + !t.revoked && !t.x_mitre_deprecated +); +``` + +#### When to Use ATT&CK IDs + +**✅ Recommended for**: + +- User interfaces and documentation +- Reports and human communication +- Manual analysis workflows +- External tool integration where human readability matters + +**❌ Not recommended for**: + +- Database primary keys (use STIX IDs) +- Programmatic object references (use STIX IDs) +- Critical system integration where uniqueness is required + +### 3. External Reference IDs: The Integration System + +**Format**: Varies by external framework +**Examples**: `NIST-Mobile-ID`, `CAPEC-123` + +#### Purpose and Usage + +External reference IDs link ATT&CK objects to concepts in other security frameworks and standards. + +**Common external frameworks**: + +- **NIST Mobile Threat Catalogue**: Found on Mobile domain techniques +- **CAPEC (Common Attack Pattern Enumeration and Classification)**: Found on Enterprise techniques +- **CVE references**: Found on software objects for specific vulnerabilities + +#### Storage Pattern + +Multiple external references can exist beyond the primary ATT&CK ID: + +```json +{ + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1055", + "url": "https://attack.mitre.org/techniques/T1055" + }, + { + "source_name": "capec", + "external_id": "CAPEC-640", + "url": "https://capec.mitre.org/data/definitions/640.html" + } + ] +} +``` + +#### When to Use External Reference IDs + +**✅ Recommended for**: + +- Cross-framework mapping and correlation +- Integration with other security standards +- Research that spans multiple threat intelligence frameworks +- Compliance reporting that requires framework cross-references + +## Identifier Resolution Patterns + +### Lookup by ATT&CK ID + +The most common query pattern involves finding objects by human-readable ATT&CK ID: + +```typescript +// Manual approach - searching external references +const technique = attackDataModel.techniques.find(t => + t.external_references?.[0]?.external_id === "T1055" && + t.external_references?.[0]?.source_name === "mitre-attack" +); + +// Library approach - optimized lookup +const technique = attackDataModel.getTechniqueByAttackId("T1055"); +``` + +**Performance consideration**: ATT&CK ID lookups require searching external references arrays, which is less efficient than STIX ID lookups. Libraries typically build indexes to optimize this pattern. + +### Bidirectional Conversion + +Applications often need to convert between identifier types: + +```typescript +// ATT&CK ID → STIX ID +const stixId = attackDataModel.getStixIdFromAttackId("T1055"); + +// STIX ID → ATT&CK ID +const attackId = attackDataModel.getAttackIdFromStixId( + "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" +); + +// Object → Both IDs +const technique = attackDataModel.getTechniqueByAttackId("T1055"); +console.log(`STIX ID: ${technique.id}`); +console.log(`ATT&CK ID: ${technique.getAttackId()}`); +``` + +### Relationship Reference Resolution + +Relationships use STIX IDs, but applications may need to display ATT&CK IDs: + +```json +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "intrusion-set--stix-id-for-apt1", + "target_ref": "attack-pattern--stix-id-for-t1055" +} +``` + +```typescript +// Display relationship with human-readable IDs +const relationship = /* ... */; +const group = attackDataModel.getGroupById(relationship.source_ref); +const technique = attackDataModel.getTechniqueById(relationship.target_ref); + +console.log(`${group.getAttackId()} uses ${technique.getAttackId()}`); +// Output: "G0006 uses T1055" +``` + +## Identifier Evolution and Management + +### ID Assignment Process + +#### STIX IDs + +- **Generated**: Automatically created using UUID generation algorithms +- **Immutable**: Never change once assigned, even when object content is updated +- **Globally coordinated**: UUID algorithms ensure uniqueness without central coordination + +#### ATT&CK IDs + +- **Curated**: Manually assigned by MITRE ATT&CK team following semantic patterns +- **Mostly stable**: Generally preserved across object updates, but may change in exceptional circumstances +- **Centrally coordinated**: MITRE maintains the canonical assignment registry + +### Version Management Impact + +Identifier behavior during object evolution: + +#### Content Updates + +```json +{ + "id": "attack-pattern--unchanged-stix-id", + "external_references": [ + { + "external_id": "T1055", // ATT&CK ID typically unchanged + "source_name": "mitre-attack" + } + ], + "x_mitre_version": "2.0", // Version increments + "modified": "2023-06-15T10:30:00.000Z" // Timestamp updates +} +``` + +#### Object Replacement (Revocation) + +When objects are substantially restructured, they may be revoked and replaced: + +```json +// Old object +{ + "id": "attack-pattern--old-stix-id", + "revoked": true +} + +// Replacement relationship +{ + "type": "relationship", + "relationship_type": "revoked-by", + "source_ref": "attack-pattern--old-stix-id", + "target_ref": "attack-pattern--new-stix-id" +} +``` + +**Impact**: Both STIX and ATT&CK IDs may change during revocation, requiring applications to follow replacement chains. + +## Common Identifier Mistakes + +### 1. Using ATT&CK IDs as Primary Keys + +**❌ Problematic**: + +```typescript +// ATT&CK IDs are not guaranteed unique +const techniqueCache = new Map(); +technique.forEach(t => { + techniqueCache.set(t.getAttackId(), t); // May overwrite due to collisions +}); +``` + +**✅ Correct**: + +```typescript +// Use STIX IDs for reliable uniqueness +const techniqueCache = new Map(); +techniques.forEach(t => { + techniqueCache.set(t.id, t); // STIX IDs are guaranteed unique +}); +``` + +### 2. Hard-coding STIX IDs + +**❌ Problematic**: + +```typescript +// STIX IDs are not meaningful to humans +if (technique.id === "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298") { + // This is unreadable and unmaintainable +} +``` + +**✅ Correct**: + +```typescript +// Use ATT&CK IDs for human-readable logic +if (technique.getAttackId() === "T1055") { + // Clear intent and maintainable +} +``` + +### 3. Assuming ATT&CK ID Stability + +**❌ Problematic**: + +```typescript +// Assuming ATT&CK IDs never change +const savedAttackId = "T1055"; +// Later... (may fail if ID was reassigned) +const technique = getTechniqueByAttackId(savedAttackId); +``` + +**✅ Better**: + +```typescript +// Store STIX IDs for long-term reliability +const savedStixId = technique.id; +// Later... (guaranteed to work unless object is revoked) +const technique = getTechniqueById(savedStixId); +``` + +### 4. Ignoring External Reference Structure + +**❌ Problematic**: + +```typescript +// Assuming ATT&CK ID is always first external reference +const attackId = technique.external_references[0].external_id; +``` + +**✅ Correct**: + +```typescript +// Properly validate external reference structure +const attackRef = technique.external_references?.find(ref => + ref.source_name === "mitre-attack" && ref.external_id +); +const attackId = attackRef?.external_id; +``` + +## Best Practices for Identifier Management + +### 1. Use the Right Identifier for the Task + +**Storage and processing**: Use STIX IDs +**User interfaces and communication**: Use ATT&CK IDs +**Cross-framework integration**: Use external reference IDs + +### 2. Build Lookup Indexes + +```typescript +class AttackDataModel { + private stixToAttackId = new Map(); + private attackIdToStix = new Map(); + + constructor(techniques: Technique[]) { + techniques.forEach(technique => { + const attackId = this.extractAttackId(technique); + if (attackId) { + this.stixToAttackId.set(technique.id, attackId); + this.attackIdToStix.set(attackId, technique.id); + } + }); + } + + getTechniqueByAttackId(attackId: string): Technique | undefined { + const stixId = this.attackIdToStix.get(attackId); + return stixId ? this.getTechniqueById(stixId) : undefined; + } +} +``` + +### 3. Handle Identifier Edge Cases + +```typescript +function safeGetAttackId(object: AttackObject): string | undefined { + // Handle objects without ATT&CK IDs (like relationships) + const attackRef = object.external_references?.find(ref => + ref.source_name === "mitre-attack" && ref.external_id + ); + + return attackRef?.external_id; +} +``` + +### 4. Plan for Identifier Evolution + +```typescript +// Design APIs that can adapt to identifier changes +interface TechniqueReference { + stixId: string; // Primary, stable identifier + attackId?: string; // Secondary, may change + lastValidated: Date; // Track when reference was verified +} +``` + +--- + +## The Multi-Identifier Philosophy + +ATT&CK's multiple identifier systems reflect the reality that different use cases have fundamentally different requirements. Rather than forcing a compromise that serves no use case well, the framework provides specialized identifiers optimized for specific scenarios: + +- **STIX IDs** for reliable machine processing +- **ATT&CK IDs** for effective human communication +- **External reference IDs** for framework integration + +Understanding when and how to use each identifier system is essential for building robust, maintainable ATT&CK applications that serve both human and machine users effectively. + +**Next**: Explore the rationale behind **[Versioning Philosophy](./versioning-philosophy)** and how ATT&CK manages evolution across multiple dimensions. diff --git a/docusaurus/docs/explanation/index.md b/docusaurus/docs/explanation/index.md new file mode 100644 index 00000000..ae586dea --- /dev/null +++ b/docusaurus/docs/explanation/index.md @@ -0,0 +1,192 @@ +# Explanation + +**Understanding-oriented content about design decisions and architecture** + +This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. + +## Design Philosophy + +### Why These Explanations Matter + +Understanding the reasoning behind design decisions helps you: + +- **Make informed choices** about how to use the library in your projects +- **Extend the library** in ways that align with its architecture +- **Contribute effectively** to the project's development +- **Troubleshoot issues** by understanding underlying mechanisms +- **Architect systems** that work well with the library's strengths + +## Available Explanations + +### Foundational Context + +- **[Why the ATT&CK Data Model Exists](./why-adm-exists)** - The problem context, solution approach, and value proposition +- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX was chosen and how it shapes the architecture + +### ATT&CK Specification Understanding + +- **[ATT&CK Specification Overview](./attack-specification-overview)** - Understanding the structure and purpose of the ATT&CK specification +- **[Object Design Rationale](./object-design-rationale)** - Why ATT&CK objects are structured the way they are +- **[Identifier Systems](./identifier-systems)** - Understanding ATT&CK's multiple identification schemes and their purposes +- **[Versioning Philosophy](./versioning-philosophy)** - Understanding ATT&CK's multi-dimensional versioning approach + +### Technical Architecture + +- **[Schema Design Principles](./schema-design)** - Validation philosophy, refinement patterns, and extensibility choices + +## Core Principles + +The ATT&CK Data Model is built on several key principles that influence all design decisions: + +### 1. STIX 2.1 Compliance First + +**Principle**: Maintain strict compatibility with STIX 2.1 specification +**Implication**: All ATT&CK objects are valid STIX objects first, ATT&CK objects second +**Trade-off**: Sometimes requires more complex APIs to maintain standard compliance + +**Example**: Using STIX relationship objects instead of embedded references, even though embedded references might be more convenient for some use cases. + +### 2. Type Safety Without Performance Cost + +**Principle**: Provide strong TypeScript types while maintaining runtime performance +**Implication**: Heavy use of Zod schemas for both validation and type inference +**Trade-off**: Larger bundle size in exchange for compile-time safety and runtime validation + +**Example**: Every ATT&CK object has both a Zod schema (for validation) and TypeScript interface (for typing), even though this creates some duplication. + +### 3. Relationship-First Navigation + +**Principle**: ATT&CK's value comes from object relationships, so navigation must be intuitive +**Implication**: Implementation classes provide relationship methods that abstract STIX relationship complexity +**Trade-off**: Memory overhead for relationship indexing in exchange for fast navigation + +**Example**: `technique.getTactics()` abstracts the underlying STIX relationships and provides immediate access to related objects. + +### 4. Extensibility Through Standards + +**Principle**: Support customization without breaking compatibility +**Implication**: Extensions follow STIX custom property conventions +**Trade-off**: More verbose extension syntax but guaranteed interoperability + +**Example**: Custom fields use `x_custom_` prefixes and require manual refinement reapplication, following STIX best practices. + +## Architectural Themes + +### Layered Architecture + +The library uses a three-layer architecture: + +1. **Schema Layer** - Zod schemas for validation and type inference +2. **Class Layer** - Implementation classes with relationship navigation +3. **Data Access Layer** - Data sources and loading mechanisms + +Each layer serves distinct purposes and can be used independently or together. + +### Immutable Data Model + +**Decision**: ATT&CK objects are immutable after loading +**Rationale**: Prevents accidental modification of shared threat intelligence data +**Implication**: Any modifications require creating new objects or data models + +### Lazy Relationship Resolution + +**Decision**: Relationships are resolved on-demand rather than eagerly +**Rationale**: Better memory usage and faster initial loading +**Implication**: First access to relationships may be slightly slower than subsequent accesses + +## Design Evolution + +### Historical Context + +The ATT&CK Data Model evolved through several design phases: + +1. **Simple JSON Processing** - Direct JSON manipulation without validation +2. **Schema-First Design** - Introduction of Zod validation schemas +3. **Relationship Navigation** - Addition of implementation classes with navigation methods +4. **Multi-Source Support** - Extensible data source architecture +5. **Type Safety** - Full TypeScript integration with proper type inference + +Each evolution maintained backward compatibility while addressing real-world usage patterns. + +### Current Design State + +The current architecture reflects lessons learned from: + +- **Enterprise deployments** requiring strict validation +- **Research applications** needing flexible data exploration +- **Integration scenarios** demanding standards compliance +- **Performance requirements** in high-throughput systems + +## Understanding Through Examples + +### Why Zod Instead of JSON Schema? + +**Context**: Need for runtime validation and TypeScript integration +**Decision**: Use Zod for schema definition +**Alternatives Considered**: JSON Schema, Joi, Yup + +**Rationale**: + +- Zod provides TypeScript type inference automatically +- Runtime validation matches compile-time types exactly +- Schema definitions are more maintainable in TypeScript +- Better error messages for developers + +**Trade-offs**: + +- Zod is less universally known than JSON Schema +- Schemas are tied to TypeScript ecosystem +- Larger runtime bundle due to Zod dependency + +### Why Implementation Classes Instead of Plain Objects? + +**Context**: Need for relationship navigation without breaking STIX compliance +**Decision**: Create implementation classes that extend plain objects with methods +**Alternatives Considered**: Plain objects with utility functions, custom object shapes + +**Rationale**: + +- Object-oriented navigation is more intuitive (`technique.getTactics()`) +- Encapsulation keeps relationship logic organized +- TypeScript provides better IntelliSense for methods +- Classes can be extended by library users + +**Trade-offs**: + +- Increased memory usage due to method inheritance +- More complex object construction process +- Learning curve for users expecting plain JSON objects + +## When to Read These Explanations + +### Before Building Applications + +Read **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand whether this library fits your use case and how it compares to alternatives. + +### When Working with ATT&CK Data + +Read **[ATT&CK Specification Overview](./attack-specification-overview)** to understand the structure and purpose of the ATT&CK specification, then **[Identifier Systems](./identifier-systems)** to understand how to work with ATT&CK's multiple identification schemes effectively. + +### When Building Data Processing Logic + +Read **[Object Design Rationale](./object-design-rationale)** to understand why ATT&CK objects are structured the way they are, and **[Versioning Philosophy](./versioning-philosophy)** to handle evolution and compatibility correctly. + +### When Extending Functionality + +Read **[Schema Design Principles](./schema-design)** to understand how to extend schemas and create custom validation rules that align with the library's patterns. + +### When Contributing Code + +Read **[STIX 2.1 as the Foundation](./stix-foundation)** to understand the standards compliance requirements that guide all development decisions. + +## Discussion and Feedback + +These explanations reflect the current understanding and rationale behind design decisions. If you have questions about specific choices or suggestions for different approaches: + +- **Open a GitHub Discussion** for architectural questions +- **Create an Issue** for specific design concerns +- **Join the community** to discuss alternative approaches + +--- + +**Ready to dive deeper?** Start with **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand the foundational context that shapes everything else. diff --git a/docusaurus/docs/explanation/object-design-rationale.md b/docusaurus/docs/explanation/object-design-rationale.md new file mode 100644 index 00000000..9887e9f1 --- /dev/null +++ b/docusaurus/docs/explanation/object-design-rationale.md @@ -0,0 +1,457 @@ +# Object Design Rationale + +**Why ATT&CK objects are structured the way they are** + +The design of ATT&CK objects reflects careful consideration of competing requirements: STIX standards compliance, semantic accuracy, performance implications, and usability trade-offs. Each design decision represents a specific choice between alternatives, with clear rationales and acknowledged trade-offs. Understanding these decisions helps you work more effectively with ATT&CK data and make informed choices when extending the framework. + +## Design Philosophy + +### Semantic Fidelity Over Simplicity + +ATT&CK object design prioritizes accurate representation of adversary behavior concepts over implementation convenience. This philosophy manifests in several ways: + +#### Rich Metadata Preservation + +Objects retain all contextual information that might be useful for analysis, even if it increases complexity: + +```json +{ + "type": "attack-pattern", + "name": "Process Injection", + "x_mitre_platforms": ["Windows", "Linux", "macOS"], + "x_mitre_system_requirements": ["Administrator privileges may be required"], + "x_mitre_permissions_required": ["User"], + "x_mitre_effective_permissions": ["Administrator"], + "x_mitre_defense_bypassed": ["Anti-virus", "Application control"], + "x_mitre_remote_support": true +} +``` + +**Rationale**: Adversary techniques exist in complex operational contexts. Simplifying away this complexity would reduce the framework's analytical value. + +#### Explicit Relationship Modeling + +Rather than embedding relationships as simple references, ATT&CK uses explicit relationship objects that can carry their own metadata: + +```json +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "intrusion-set--12345...", + "target_ref": "attack-pattern--67890...", + "description": "APT1 has used Process Injection to execute code within the address space of another process...", + "x_mitre_version": "1.0" +} +``` + +**Benefits**: Procedure descriptions, confidence levels, and temporal information can be associated with specific technique usage patterns. + +**Trade-offs**: More complex query patterns and increased memory usage compared to embedded references. + +### Standards Compliance Over Custom Optimization + +Object designs maintain STIX 2.1 compliance even when custom formats might be more efficient or convenient. + +#### STIX ID Requirements + +Every object must have a globally unique STIX ID, even though ATT&CK IDs would be more human-readable: + +```json +{ + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1055", + "url": "https://attack.mitre.org/techniques/T1055" + } + ] +} +``` + +**Rationale**: STIX ID uniqueness enables reliable cross-system integration and prevents identifier collisions. + +**Trade-offs**: Less intuitive programmatic access patterns compared to human-readable identifiers. + +## Object Type Decisions + +### Techniques as attack-pattern Objects + +**Decision**: Techniques use the standard STIX `attack-pattern` type rather than a custom type. + +**Rationale**: + +- Techniques represent specific methods of attack, which aligns perfectly with STIX's `attack-pattern` concept +- Leverages existing STIX tooling and analyst familiarity +- Avoids unnecessary custom types when standard STIX types suffice + +**Alternative considered**: Custom `x-mitre-technique` type +**Why rejected**: Would duplicate standard STIX functionality without adding semantic value + +**Implementation details**: + +```json +{ + "type": "attack-pattern", + "kill_chain_phases": [ + { + "kill_chain_name": "mitre-attack", + "phase_name": "execution" + } + ], + "x_mitre_is_subtechnique": false +} +``` + +### Tactics as Custom Objects + +**Decision**: Tactics use a custom `x-mitre-tactic` type rather than extending an existing STIX type. + +**Rationale**: + +- Tactics represent adversary goals/objectives, which don't map cleanly to any standard STIX type +- The closest STIX equivalent (`x_mitre_tactic`) would require awkward semantic stretching +- Custom type allows clear semantic definition and appropriate properties + +**Alternative considered**: Extending `attack-pattern` or creating tactic-specific `kill-chain-phase` definitions +**Why rejected**: Tactics are fundamentally different from attack patterns and deserve their own semantic space + +**Implementation details**: + +```json +{ + "type": "x-mitre-tactic", + "x_mitre_shortname": "execution", + "name": "Execution", + "description": "The adversary is trying to run malicious code." +} +``` + +### Groups as intrusion-set Objects + +**Decision**: Groups use the standard STIX `intrusion-set` type. + +**Rationale**: + +- Threat actor groups align precisely with STIX's `intrusion-set` concept +- Leverages standard STIX properties for attribution, motivation, and sophistication +- Enables integration with threat intelligence feeds using the same object type + +**Implementation details**: + +```json +{ + "type": "intrusion-set", + "name": "APT1", + "aliases": ["Comment Crew", "PLA Unit 61398"], + "first_seen": "2006-01-01T00:00:00.000Z" +} +``` + +### Software as malware/tool Objects + +**Decision**: Software uses standard STIX `malware` and `tool` types based on malicious intent. + +**Rationale**: + +- Distinguishes between software created for malicious purposes (`malware`) and legitimate tools used maliciously (`tool`) +- Aligns with existing security industry classification practices +- Leverages standard STIX properties for software analysis + +**Classification criteria**: + +- **malware**: Software created primarily for malicious purposes +- **tool**: Legitimate software that can be used for malicious purposes + +**Alternative considered**: Single custom `x-mitre-software` type +**Why rejected**: Would lose the important semantic distinction between purpose-built malware and dual-use tools + +### Mitigations as course-of-action Objects + +**Decision**: Mitigations use the standard STIX `course-of-action` type. + +**Rationale**: + +- Defensive recommendations align perfectly with STIX's `course-of-action` concept +- No custom properties needed beyond standard STIX fields +- Enables integration with broader defensive planning and STIX-based defensive frameworks + +### Sub-techniques as Specialized attack-patterns + +**Decision**: Sub-techniques use the same `attack-pattern` type as parent techniques, distinguished by properties and relationships. + +**Design pattern**: + +```json +{ + "type": "attack-pattern", + "x_mitre_is_subtechnique": true, + // Connected via subtechnique-of relationship +} +``` + +**Rationale**: + +- Sub-techniques are specialized techniques, not fundamentally different objects +- Inheritance of properties and behaviors from parent techniques is natural +- Avoids artificial distinction between technique levels + +**Alternative considered**: Custom `x-mitre-subtechnique` type +**Why rejected**: Would create artificial barriers to code reuse and conceptual understanding + +## Property Design Patterns + +### The x_mitre_ Namespace Strategy + +**Decision**: All ATT&CK-specific properties use the `x_mitre_` prefix. + +**Benefits**: + +- Clearly identifies ATT&CK extensions from standard STIX properties +- Prevents naming conflicts with future STIX standard properties +- Signals custom property status to STIX-compliant parsers +- Enables selective processing of standard vs. extended properties + +**Implementation pattern**: + +```json +{ + "name": "Process Injection", // Standard STIX + "description": "Adversaries may inject...", // Standard STIX + "x_mitre_platforms": ["Windows"], // ATT&CK extension + "x_mitre_version": "1.2" // ATT&CK extension +} +``` + +### Platform Property Design + +**Decision**: Platforms are represented as string arrays rather than enumerated types or structured objects. + +**Rationale**: + +- Flexibility to add new platforms without schema changes +- Simple querying and filtering patterns +- Aligns with how analysts naturally think about platform applicability + +**Alternative considered**: Structured platform objects with version and edition details +**Why rejected**: Added complexity without clear analytical benefit for most use cases + +**Implementation**: + +```json +{ + "x_mitre_platforms": ["Windows", "Linux", "macOS", "Android", "iOS"] +} +``` + +### Version Property Design + +**Decision**: Object versions use semantic versioning strings (`"1.0"`, `"1.1"`, `"2.0"`). + +**Rationale**: + +- Human-readable version comparisons +- Standard semantic versioning practices +- Enables version-aware processing and compatibility checks + +**Alternative considered**: Integer version numbers or timestamp-based versioning +**Why rejected**: Less expressive for indicating the magnitude of changes + +### Boolean vs. Enumerated Properties + +**Decision**: Use boolean properties for binary distinctions (`x_mitre_is_subtechnique`) and string arrays for multi-valued properties (`x_mitre_platforms`). + +**Design principle**: Match the property type to the natural cardinality of the concept: + +```json +{ + "x_mitre_is_subtechnique": true, // Binary distinction + "x_mitre_platforms": ["Windows", "Linux"], // Multi-valued + "x_mitre_remote_support": false // Binary capability +} +``` + +## Relationship Design Patterns + +### Explicit vs. Embedded Relationships + +**Decision**: Use explicit STIX relationship objects rather than embedded references. + +**Pattern**: + +```json +// ❌ Embedded approach +{ + "type": "attack-pattern", + "mitigated_by": ["course-of-action--12345...", "course-of-action--67890..."] +} + +// ✅ Explicit relationship approach +{ + "type": "relationship", + "relationship_type": "mitigates", + "source_ref": "course-of-action--12345...", + "target_ref": "attack-pattern--67890...", + "description": "Specific guidance on how this mitigation applies..." +} +``` + +**Benefits**: + +- Relationships can carry metadata (descriptions, confidence, temporal information) +- Bidirectional navigation is naturally supported +- Relationship evolution doesn't require object schema changes +- Complex relationship patterns are easily represented + +**Trade-offs**: + +- Increased memory usage for relationship storage +- More complex query patterns for relationship traversal +- Additional processing overhead for relationship resolution + +### Custom Relationship Types + +**Decision**: Define custom relationship types for ATT&CK-specific associations. + +**Examples**: + +- `subtechnique-of`: Links sub-techniques to parent techniques +- `detects`: Links detection strategies to techniques +- `mitigates`: Links defensive measures to techniques (standard STIX but worth noting) + +**Rationale**: ATT&CK relationships have specific semantics that generic relationship types cannot capture accurately. + +### Relationship Direction Consistency + +**Decision**: Establish consistent direction patterns for relationship types: + +- `subtechnique-of`: sub-technique → parent technique +- `detects`: detection capability → technique +- `mitigates`: defensive measure → technique +- `uses`: actor/software → technique/software + +**Benefits**: Predictable query patterns and consistent mental models for relationship navigation. + +## Performance vs. Semantics Trade-offs + +### Memory Usage Decisions + +**Choice**: Prioritize semantic accuracy over memory efficiency. + +**Implications**: + +- Rich metadata is preserved even if rarely used +- Explicit relationships consume more memory than embedded references +- Full object graphs are maintained rather than lazy-loading references + +**Rationale**: ATT&CK is primarily used for analysis rather than high-volume transaction processing, so semantic richness typically outweighs memory concerns. + +### Query Complexity Decisions + +**Choice**: Accept query complexity in exchange for relationship flexibility. + +**Example**: Finding all mitigations for a technique requires relationship traversal: + +```typescript +// Complex but flexible +const mitigations = bundle.objects + .filter(obj => + obj.type === 'relationship' && + obj.relationship_type === 'mitigates' && + obj.target_ref === technique.id + ) + .map(rel => bundle.objects.find(obj => obj.id === rel.source_ref)); + +// vs. simple but inflexible embedded approach +const mitigations = technique.mitigated_by.map(id => + bundle.objects.find(obj => obj.id === id) +); +``` + +**Mitigation**: The ATT&CK Data Model library pre-processes relationships into indexes to restore O(1) lookup performance. + +### Validation Complexity Decisions + +**Choice**: Implement comprehensive validation at the cost of processing overhead. + +**Rationale**: Data quality issues in ATT&CK datasets can cascade through analysis workflows, making upfront validation cost-effective despite performance overhead. + +**Implementation strategy**: Provide both strict and relaxed validation modes to balance quality and performance based on use case requirements. + +## Extension Point Design + +### Custom Property Extensibility + +**Decision**: Allow arbitrary custom properties following STIX naming conventions. + +**Pattern**: + +```json +{ + "type": "attack-pattern", + "name": "Process Injection", + "x_mitre_platforms": ["Windows"], // Standard ATT&CK extension + "x_org_threat_level": "high", // Organization-specific extension + "x_custom_last_observed": "2023-01-15" // Custom tracking property +} +``` + +**Benefits**: Organizations can add domain-specific metadata without breaking standards compliance. + +**Constraints**: Custom properties must follow STIX naming conventions (`x_` prefix) and not conflict with existing properties. + +### Schema Evolution Patterns + +**Decision**: Use additive schema evolution with explicit deprecation cycles. + +**Process**: + +1. New properties are added as optional fields +2. Deprecated properties are marked but remain functional +3. Removal occurs only at major version boundaries with migration guidance + +**Benefits**: Backward compatibility is maintained while enabling specification evolution. + +## Living with Design Complexity + +### When Object Design Feels Overwhelming + +ATT&CK object design can seem complex because it encodes the full semantic richness of adversary behavior analysis. This complexity is intentional and necessary: + +- **STIX compliance** requires numerous standard properties and relationships +- **Semantic accuracy** demands precise representation of adversary concepts +- **Extensibility** needs mechanisms that maintain standards compliance +- **Performance** optimization requires careful trade-off management + +### Simplification Strategies + +When working with ATT&CK objects: + +1. **Use implementation classes** - Higher-level APIs abstract object complexity +2. **Focus on your use case** - You rarely need to understand all object properties +3. **Leverage validation feedback** - Let schema validation guide correct usage +4. **Start with examples** - Working code demonstrates proper object usage patterns + +### Contributing to Object Design + +Understanding these design rationales helps you: + +- **Propose extensions** that align with existing patterns +- **Identify genuine design problems** vs. complexity that serves a purpose +- **Suggest performance improvements** that preserve semantic accuracy +- **Evaluate trade-offs** when extending the framework + +--- + +## The Design Philosophy in Practice + +Object design decisions reflect a consistent philosophy that prioritizes: + +1. **Standards compliance over convenience** +2. **Semantic accuracy over simplicity** +3. **Long-term sustainability over short-term optimization** +4. **Community interoperability over isolated efficiency** + +Understanding this philosophy helps you work effectively with ATT&CK objects and contribute meaningfully to the framework's evolution. + +**Next**: Explore how **[Identifier Systems](./identifier-systems)** enable both machine processing and human comprehension of ATT&CK data. diff --git a/docusaurus/docs/explanation/schema-design.md b/docusaurus/docs/explanation/schema-design.md new file mode 100644 index 00000000..45725a66 --- /dev/null +++ b/docusaurus/docs/explanation/schema-design.md @@ -0,0 +1,478 @@ +# Schema Design Principles + +**Validation philosophy, refinement patterns, and extensibility choices** + +The schema layer is the foundation that enables both runtime validation and compile-time type safety in the ATT&CK Data Model. The design of these schemas reflects careful consideration of competing concerns: STIX compliance, ATT&CK domain rules, TypeScript integration, extensibility, and performance. This explanation explores the principles that guide schema design decisions. + +## Core Design Philosophy + +### Validation as Documentation + +**Principle**: Schemas serve as executable documentation of ATT&CK requirements. + +Rather than maintaining separate documentation about data format requirements, the schemas encode these requirements directly. This ensures that documentation stays synchronized with implementation and provides immediate feedback when requirements change. + +```typescript +// Schema encodes the requirement that techniques must have ATT&CK IDs +const techniqueSchema = z.object({ + external_references: z.array(externalReferenceSchema).min(1) +}).superRefine((data, ctx) => { + const firstRef = data.external_references[0]; + if (firstRef.source_name !== 'mitre-attack' || !firstRef.external_id) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'First external reference must contain ATT&CK ID' + }); + } +}); +``` + +This approach means that anyone using the schemas automatically understands ATT&CK requirements through validation feedback, rather than needing to consult separate documentation. + +### Type Inference Over Type Declaration + +**Principle**: TypeScript types are inferred from schemas, not declared separately. + +This prevents the common problem of types becoming out of sync with runtime validation: + +```typescript +// ✅ Single source of truth - types inferred from schema +const techniqueSchema = z.object({ + name: z.string(), + x_mitre_platforms: z.array(z.string()).optional() +}); +type Technique = z.infer; + +// ❌ Avoided - separate type declarations that can drift +interface Technique { + name: string; + x_mitre_platforms?: string[]; +} +const techniqueSchema = z.object(/* ... */); +``` + +**Benefits**: + +- Impossible for types to become inconsistent with validation +- Schema changes automatically propagate to TypeScript types +- Refactoring tools work across both validation and type layers + +**Trade-offs**: + +- More complex TypeScript types that reflect schema structure +- Limited ability to create "prettier" type declarations +- Schema design is constrained by TypeScript type inference capabilities + +## Schema Architecture Patterns + +### Hierarchical Schema Composition + +ATT&CK schemas are built through composition rather than inheritance, reflecting the layered nature of STIX + ATT&CK requirements: + +```typescript +// Base STIX requirements +const stixDomainObjectSchema = z.object({ + type: z.string(), + spec_version: z.literal('2.1'), + id: stixIdentifierSchema, + created: stixTimestampSchema, + modified: stixTimestampSchema +}); + +// ATT&CK-specific additions +const attackBaseObjectSchema = z.object({ + name: z.string().min(1), + description: z.string().min(1), + x_mitre_attack_spec_version: z.string(), + x_mitre_version: z.string(), + x_mitre_domains: z.array(z.enum(['enterprise-attack', 'mobile-attack', 'ics-attack'])), + external_references: z.array(externalReferenceSchema).min(1) +}); + +// Object-specific requirements +const techniqueSchema = stixDomainObjectSchema + .merge(attackBaseObjectSchema) + .extend({ + type: z.literal('attack-pattern'), + kill_chain_phases: z.array(killChainPhaseSchema), + x_mitre_platforms: z.array(z.string()).optional(), + x_mitre_is_subtechnique: z.boolean().optional() + }); +``` + +**Design Rationale**: + +- **Composition over inheritance** enables flexible schema reuse +- **Layer separation** makes it clear which requirements come from which standards +- **Merge vs extend** choice depends on whether properties can conflict + +### Refinement Pattern for Complex Validation + +Simple property validation isn't sufficient for ATT&CK's business rules. The refinement pattern handles cross-property and contextual validation: + +```typescript +const techniqueWithRefinements = techniqueSchema + .superRefine(createAttackIdRefinement()) + .superRefine(createTacticRefinement()) + .superRefine(createSubtechniqueRefinement()); + +// Refinements are factory functions for reusability +function createAttackIdRefinement() { + return (data: any, ctx: z.RefinementCtx) => { + const firstRef = data.external_references?.[0]; + if (!firstRef || firstRef.source_name !== 'mitre-attack') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'First external reference must be ATT&CK ID', + path: ['external_references', 0] + }); + } + + // Validate ATT&CK ID format + const attackId = firstRef.external_id; + if (attackId && !isValidAttackId(attackId, data.type)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Invalid ATT&CK ID format: ${attackId}`, + path: ['external_references', 0, 'external_id'] + }); + } + }; +} +``` + +**Why Factory Functions**: + +- **Reusability** across multiple schemas +- **Testability** in isolation from schemas +- **Composability** - refinements can be mixed and matched +- **Extensibility** - users can add their own refinements + +## Extensibility Architecture + +### The Refinement Challenge + +Zod's extension methods (`extend`, `merge`, `pick`, `omit`) discard refinements, creating a challenge for extensibility: + +```typescript +// ❌ This loses refinements +const extendedTechnique = techniqueSchema.extend({ + x_custom_field: z.string() +}); + +// ✅ This preserves refinements +const extendedTechnique = techniqueSchema + .extend({ x_custom_field: z.string() }) + .superRefine(createAttackIdRefinement()) // Must reapply manually + .superRefine(createTacticRefinement()); +``` + +**Design Response**: + +1. **Export refinement factories** so users can reapply them +2. **Document which refinements** each schema uses +3. **Provide extension helpers** that automate refinement reapplication + +```typescript +// Helper function for common extension pattern +export function extendAttackSchema( + baseSchema: z.ZodObject, + extensions: U, + refinements: Array<(data: any, ctx: z.RefinementCtx) => void> +) { + let extended = baseSchema.extend(extensions); + for (const refinement of refinements) { + extended = extended.superRefine(refinement); + } + return extended; +} + +// Usage +const customTechnique = extendAttackSchema( + techniqueSchema, + { x_org_threat_level: z.enum(['low', 'medium', 'high']) }, + [createAttackIdRefinement(), createTacticRefinement()] +); +``` + +### Custom Property Conventions + +STIX custom property naming conventions are enforced through schema design: + +```typescript +// Custom properties must use x_ prefix +const customPropertySchema = z.record( + z.string().regex(/^x_[a-z0-9_]+$/), // Enforce naming convention + z.unknown() // Allow any value type +); + +const extensibleObjectSchema = baseSchema.and(customPropertySchema); +``` + +**Benefits**: + +- **Standards compliance** is automatically enforced +- **Namespace conflicts** are prevented +- **Tool interoperability** is preserved + +**Limitations**: + +- **Naming restrictions** may feel constraining +- **TypeScript integration** is more complex with dynamic properties +- **Validation messages** may be unclear for naming violations + +## Validation Philosophy + +### Strict vs Relaxed Modes + +The schema design supports two validation philosophies through parsing modes: + +#### Strict Mode Philosophy + +**"Data integrity is paramount - invalid data must be rejected completely"** + +```typescript +// Strict mode - all objects must validate +const result = techniqueSchema.parse(data); // Throws on any error +``` + +**Use cases**: + +- Production applications requiring data quality guarantees +- Integration with systems that can't handle partial data +- Testing and validation scenarios + +#### Relaxed Mode Philosophy + +**"Partial data is better than no data - log errors but continue processing"** + +```typescript +// Relaxed mode - continue with valid objects +const result = techniqueSchema.safeParse(data); +if (!result.success) { + console.warn('Validation warnings:', result.error.errors); + return null; // Skip invalid objects but continue processing +} +return result.data; +``` + +**Use cases**: + +- Research and development environments +- Integration with data sources that may have minor quality issues +- Gradual data migration scenarios + +### Error Message Design + +Schema error messages are designed for developer clarity rather than end-user consumption: + +```typescript +// Good error message design +ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Technique must specify at least one platform in x_mitre_platforms', + path: ['x_mitre_platforms'] +}); + +// Poor error message design +ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid', // Too vague + path: [] // No specific location +}); +``` + +**Design principles**: + +- **Specific location** - use path arrays to pinpoint issues +- **Actionable guidance** - explain what needs to be fixed +- **Context awareness** - reference ATT&CK concepts, not just schema structure +- **Technical accuracy** - precise terminology for developers + +## Performance Considerations + +### Schema Compilation Strategy + +Schemas are designed to compile once and reuse many times: + +```typescript +// ✅ Compile schema once +const compiledSchema = techniqueSchema; + +// ✅ Reuse for multiple validations +data.forEach(item => compiledSchema.parse(item)); + +// ❌ Don't recompile schemas +data.forEach(item => z.object({/* ... */}).parse(item)); +``` + +**Optimization strategies**: + +- **Schema caching** to avoid recompilation +- **Partial compilation** for frequently-used sub-schemas +- **Lazy loading** for rarely-used validation rules + +### Memory vs Speed Tradeoffs + +Schema design balances memory usage against validation speed: + +```typescript +// Memory-efficient but slower - shared schema instances +const baseSchema = z.object({/* large schema */}); +const schema1 = baseSchema.extend({/* additions */}); +const schema2 = baseSchema.extend({/* different additions */}); + +// Faster but memory-intensive - separate compiled schemas +const schema1 = z.object({/* complete schema */}); +const schema2 = z.object({/* complete schema with duplicated parts */}); +``` + +The library chooses **memory efficiency over raw speed** because: + +- ATT&CK datasets are large but validation happens infrequently +- Schema compilation time is amortized across many validations +- Memory usage affects application scalability more than validation speed + +## Testing Strategy for Schemas + +### Positive and Negative Test Cases + +Each schema includes comprehensive test coverage: + +```typescript +describe('techniqueSchema', () => { + it('accepts valid technique objects', () => { + const validTechnique = { + type: 'attack-pattern', + id: 'attack-pattern--12345678-1234-1234-1234-123456789012', + spec_version: '2.1', + created: '2023-01-01T00:00:00.000Z', + modified: '2023-01-01T00:00:00.000Z', + name: 'Process Injection', + description: 'Adversaries may inject code...', + external_references: [{ + source_name: 'mitre-attack', + external_id: 'T1055', + url: 'https://attack.mitre.org/techniques/T1055' + }], + x_mitre_attack_spec_version: '3.3.0', + x_mitre_version: '1.0' + }; + + expect(() => techniqueSchema.parse(validTechnique)).not.toThrow(); + }); + + it('rejects techniques without ATT&CK IDs', () => { + const invalidTechnique = { + // Valid STIX structure but missing ATT&CK ID + type: 'attack-pattern', + id: 'attack-pattern--12345678-1234-1234-1234-123456789012', + // ... other required fields + external_references: [{ + source_name: 'other-source', // Not mitre-attack + external_id: 'OTHER-001' + }] + }; + + expect(() => techniqueSchema.parse(invalidTechnique)).toThrow(); + }); +}); +``` + +### Real-World Data Testing + +Schemas are tested against actual ATT&CK releases: + +```typescript +// Test against production ATT&CK data +describe('schema compatibility', () => { + it('validates current ATT&CK enterprise data', async () => { + const enterpriseData = await loadAttackData('enterprise-attack', '15.1'); + + enterpriseData.objects.forEach(obj => { + if (obj.type === 'attack-pattern') { + expect(() => techniqueSchema.parse(obj)).not.toThrow(); + } + }); + }); +}); +``` + +This ensures schemas remain compatible with real ATT&CK data evolution. + +## Future Evolution Patterns + +### Schema Versioning Strategy + +Schemas evolve alongside ATT&CK specifications: + +```typescript +// Version-aware schema selection +const getSchemaForVersion = (attackVersion: string) => { + if (attackVersion >= '3.3.0') { + return techniqueSchemaV3_3; + } else if (attackVersion >= '3.0.0') { + return techniqueSchemaV3_0; + } else { + return legacyTechniqueSchema; + } +}; +``` + +**Migration strategy**: + +- **Backward compatibility** for older ATT&CK versions +- **Gradual deprecation** of obsolete schema features +- **Clear migration paths** between schema versions + +### Extension Point Evolution + +Schema extension mechanisms evolve to support new use cases: + +```typescript +// Current extension pattern +const extended = baseSchema.extend(customFields).superRefine(customRefinement); + +// Future extension pattern (conceptual) +const extended = baseSchema.withExtensions({ + fields: customFields, + refinements: [customRefinement], + metadata: { version: '2.0', namespace: 'org' } +}); +``` + +## Living with Schema Complexity + +### When Schemas Feel Too Complex + +The ATT&CK Data Model schemas can feel overwhelming because they encode the full complexity of STIX + ATT&CK requirements. This complexity is **intentional and necessary**: + +- **STIX compliance** requires numerous fields and validation rules +- **ATT&CK domain rules** add additional constraints beyond STIX +- **Type safety** demands precise specification of all possibilities +- **Extensibility** needs mechanisms that maintain standards compliance + +### Simplification Strategies + +When working with the schemas feels too complex, consider: + +1. **Use higher-level APIs** - Implementation classes abstract schema complexity +2. **Focus on your use case** - You don't need to understand all schema features +3. **Start simple** - Begin with basic objects and add complexity gradually +4. **Leverage validation feedback** - Let schema errors guide you to correct usage + +## The Schema Design Philosophy in Practice + +The schema design reflects a philosophical commitment to: + +1. **Correctness over convenience** - Better to be precise than easy +2. **Standards compliance over optimization** - STIX compatibility is non-negotiable +3. **Type safety over flexibility** - Catch errors at compile time when possible +4. **Documentation through code** - Schemas serve as executable specifications +5. **Future-proofing over current simplicity** - Design for evolution and extension + +Understanding these philosophical commitments helps you work effectively with the schema layer and contribute to its continued development. + +--- + +**Next**: Explore how these schema design decisions create **[Performance vs Flexibility Trade-offs](./trade-offs)** in the overall library architecture. diff --git a/docusaurus/docs/explanation/stix-foundation.md b/docusaurus/docs/explanation/stix-foundation.md new file mode 100644 index 00000000..4a16ecb1 --- /dev/null +++ b/docusaurus/docs/explanation/stix-foundation.md @@ -0,0 +1,454 @@ +# STIX 2.1 as the Foundation + +**Why STIX was chosen and how it shapes the architecture** + +The decision to build the ATT&CK Data Model on STIX 2.1 as a foundation was neither obvious nor simple. This explanation explores why STIX was chosen, what alternatives were considered, and how this foundational choice shapes every aspect of the library's architecture. + +## The Standards Decision + +### What is STIX 2.1? + +STIX (Structured Threat Information eXpression) 2.1 is an OASIS standard for representing cyber threat information in a structured, machine-readable format. It provides: + +- **Standardized object types** for representing threats, indicators, campaigns, and relationships +- **JSON-based format** that is both human-readable and machine-parsable +- **Relationship modeling** through explicit relationship objects +- **Extensibility mechanisms** for custom properties and object types +- **Version management** and object lifecycle handling + +### Why STIX for ATT&CK? + +MITRE chose STIX 2.1 as the distribution format for ATT&CK data for several strategic reasons: + +#### Industry Standardization + +**Rationale**: Using an industry standard ensures long-term viability and interoperability. + +**Benefits**: + +- ATT&CK data can be consumed by existing STIX-compliant tools +- Organizations can integrate ATT&CK into broader threat intelligence workflows +- No vendor lock-in - data remains in a standard, open format +- Skills transfer between ATT&CK and other STIX-based systems + +#### Rich Relationship Modeling + +**Rationale**: ATT&CK's value comes from relationships between objects (techniques, tactics, groups, etc.). + +**STIX provides**: + +- Explicit relationship objects with typed connections +- Bidirectional relationship navigation capabilities +- Complex relationship patterns (many-to-many, hierarchical, etc.) +- Metadata on relationships (confidence, timestamps, descriptions) + +#### Extensibility Without Breaking Standards + +**Rationale**: ATT&CK needs custom properties while remaining standards-compliant. + +**STIX enables**: + +- Custom properties with `x_` prefix namespace +- Custom object types with standard STIX structure +- Extension points that don't break parsers +- Clear separation between standard and custom elements + +## Architectural Implications + +The choice of STIX 2.1 as the foundation has profound implications for how the ATT&CK Data Model is architected: + +### Object Identity and References + +#### STIX ID Structure + +Every ATT&CK object has a STIX ID with the format `{type}--{uuid}`: + +```typescript +// STIX IDs are the primary identifiers +"id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" +"id": "x-mitre-tactic--78b23412-0651-46d7-a540-170a1ce8bd5a" +"id": "intrusion-set--18854f55-ac7c-4634-bd9a-352dd07613b7" +``` + +**Architectural Impact**: + +- All object lookups must handle UUID-based identifiers +- Cross-references use STIX IDs, not human-readable names +- Database storage and indexing must accommodate UUID primary keys + +#### ATT&CK IDs as External References + +Human-readable ATT&CK IDs (T1055, G0006, etc.) are stored as external references, not primary identifiers: + +```json +{ + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1055", + "url": "https://attack.mitre.org/techniques/T1055" + } + ] +} +``` + +**Architectural Impact**: + +- ATT&CK ID lookups require searching external references +- Validation must ensure first external reference contains ATT&CK ID +- APIs must provide both STIX ID and ATT&CK ID access patterns + +### Relationship Architecture + +#### Explicit Relationship Objects + +STIX models relationships as separate objects, not embedded references: + +```json +// ❌ Not STIX - embedded reference +{ + "type": "attack-pattern", + "tactics": ["TA0001", "TA0002"] +} + +// ✅ STIX - explicit relationship +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "attack-pattern--12345...", + "target_ref": "x-mitre-tactic--67890..." +} +``` + +**Architectural Impact**: + +- Relationship navigation requires separate relationship processing +- Memory overhead for maintaining relationship indexes +- Complex queries need to traverse multiple object types +- Relationship metadata (descriptions, confidence) naturally supported + +#### Bidirectional Relationship Handling + +STIX relationships are directional, but ATT&CK concepts often need bidirectional navigation: + +```typescript +// Forward: technique → tactics +const tactics = technique.getTactics(); + +// Reverse: tactic → techniques (derived from relationships) +const techniques = tactic.getTechniques(); +``` + +**Architectural Impact**: + +- Relationship indexes must support both forward and reverse lookups +- Implementation classes abstract bidirectional complexity +- Memory usage includes reverse relationship mappings + +### Validation Architecture + +#### Schema Compliance Layers + +STIX compliance creates multiple validation layers: + +1. **STIX Base Compliance**: All objects must be valid STIX objects +2. **ATT&CK Extensions**: ATT&CK-specific properties must be valid +3. **ATT&CK Business Rules**: Domain-specific validation (e.g., ATT&CK ID formats) + +```typescript +// Validation hierarchy +const techniqueSchema = stixDomainObjectSchema // STIX base + .extend(attackBaseObjectSchema.shape) // ATT&CK extensions + .extend(attackPatternSchema.shape) // Technique-specific + .superRefine(attackIdRefinement) // ATT&CK business rules + .superRefine(tacticRefinement); // Cross-object validation +``` + +**Architectural Impact**: + +- Complex validation chains with multiple failure points +- Error messages must map from schema violations to user-understandable problems +- Extension points must maintain STIX compliance + +#### Custom Property Naming + +STIX requires custom properties to use namespace prefixes: + +```json +{ + "type": "attack-pattern", + "name": "Process Injection", // Standard STIX + "x_mitre_platforms": ["Windows"], // ATT&CK custom + "x_mitre_is_subtechnique": false, // ATT&CK custom + "x_custom_confidence": 85 // User custom +} +``` + +**Architectural Impact**: + +- Naming conventions must be enforced in validation +- TypeScript types must accommodate x_ prefixed properties +- Schema extensions require careful namespace management + +## Benefits of STIX Foundation + +### Interoperability Advantages + +#### Ecosystem Integration + +Because ATT&CK data is valid STIX, it integrates naturally with: + +- **Threat Intelligence Platforms** (MISP, OpenCTI, ThreatConnect) +- **Security Orchestration** platforms with STIX support +- **Government systems** that mandate STIX compliance +- **Analyst tools** that consume STIX feeds + +#### Toolchain Compatibility + +STIX compliance enables: + +```typescript +// ATT&CK data can be processed by generic STIX tools +import { STIXParser } from 'stix-parser'; +import { registerDataSource } from '@mitre-attack/attack-data-model'; + +// Both approaches work with the same data +const genericParser = new STIXParser(); +const stixObjects = genericParser.parse(attackBundle); + +const attackSource = new DataSource({ source: 'file', file: 'attack.json' }); +const attackModel = loadDataModel(await registerDataSource(attackSource)); +``` + +### Long-term Sustainability + +#### Standards Evolution + +STIX provides a migration path for future evolution: + +- **STIX 2.2+**: Upgrading STIX versions brings new capabilities +- **Backward compatibility**: STIX versioning preserves older data +- **Community development**: STIX improvements benefit ATT&CK automatically + +#### Vendor Independence + +STIX ensures no single vendor controls the data format: + +- **Open standard**: OASIS governance prevents vendor lock-in +- **Multiple implementations**: Many parsers and tools available +- **Specification stability**: Changes go through formal standardization process + +## Costs of STIX Foundation + +### Complexity Overhead + +#### Learning Curve + +STIX introduces concepts that may be unfamiliar: + +- **UUID-based identifiers** instead of human-readable names +- **Relationship objects** instead of embedded references +- **External references** for human-readable identifiers +- **Custom property conventions** for extensions + +#### Implementation Complexity + +STIX compliance requires more sophisticated implementation: + +```typescript +// Simple approach - direct property access +const tacticName = technique.tactic; // ❌ Not how STIX works + +// STIX approach - relationship traversal +const tactics = technique.getTactics(); // ✅ Follows STIX patterns +const tacticNames = tactics.map(t => t.name); +``` + +### Performance Implications + +#### Memory Overhead + +STIX's explicit relationship modeling increases memory usage: + +- **Separate relationship objects** consume additional memory +- **Relationship indexes** for navigation speed +- **UUID storage** instead of smaller integer IDs + +#### Processing Complexity + +STIX relationship traversal is more complex: + +```typescript +// Direct approach - O(1) property access +const platforms = technique.platforms; + +// STIX approach - O(n) relationship lookup +const relationships = bundle.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'targets' +); +``` + +**Mitigation**: The library pre-processes relationships into indexes to restore O(1) lookup performance. + +## Alternative Approaches Considered + +### Custom JSON Format + +**Approach**: Design a custom, ATT&CK-optimized JSON structure. + +**Advantages**: + +- Simpler structure tailored to ATT&CK use cases +- Better performance through optimized layout +- No STIX complexity overhead + +**Disadvantages**: + +- No interoperability with existing tools +- Requires custom parsers for every integration +- No standards body governance +- Limited extension mechanisms + +**Why rejected**: Isolation from broader threat intelligence ecosystem outweighed performance benefits. + +### GraphQL Schema + +**Approach**: Use GraphQL to define ATT&CK data structure and relationships. + +**Advantages**: + +- Modern API technology with excellent tooling +- Built-in relationship traversal +- Type system with validation +- Query optimization capabilities + +**Disadvantages**: + +- Requires GraphQL server infrastructure +- Less suitable for static data distribution +- No existing threat intelligence ecosystem integration +- Over-engineering for data model use case + +**Why rejected**: Added infrastructure requirements without clear benefits over STIX. + +### Relational Database Schema + +**Approach**: Define ATT&CK as relational database tables with foreign keys. + +**Advantages**: + +- Mature technology with extensive tooling +- Excellent query capabilities through SQL +- Strong consistency guarantees +- Well-understood by most developers + +**Disadvantages**: + +- Requires database infrastructure for simple data access +- Poor fit for distributed, document-oriented data +- Limited extension mechanisms +- No standard for threat intelligence interchange + +**Why rejected**: Infrastructure requirements too heavy for a data modeling library. + +## Living with STIX Decisions + +### Embracing STIX Patterns + +Rather than fighting STIX complexity, the library embraces STIX patterns while abstracting complexity: + +#### Implementation Classes Hide Complexity + +```typescript +// STIX relationship traversal is complex... +const tacticRelationships = bundle.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'uses' && + bundle.objects.find(target => + target.id === obj.target_ref && + target.type === 'x-mitre-tactic' + ) +); + +// ...but implementation classes make it simple +const tactics = technique.getTactics(); +``` + +#### Validation Abstracts STIX Requirements + +```typescript +// Users don't need to understand STIX validation rules... +const isValid = techniqueSchema.safeParse(data).success; + +// ...the schema handles STIX compliance automatically +``` + +### Working with STIX Strengths + +The library leverages STIX strengths while mitigating weaknesses: + +#### Relationship Richness + +STIX's explicit relationships enable rich metadata: + +```typescript +// Relationship objects can carry additional context +const relationship = bundle.objects.find(obj => + obj.type === 'relationship' && + obj.relationship_type === 'uses' && + obj.source_ref === group.id && + obj.target_ref === technique.id +); + +// Access procedure descriptions from relationship +const procedureDescription = relationship.description; +``` + +#### Extension Mechanisms + +STIX custom properties enable organization-specific additions: + +```typescript +// Extend techniques with custom intelligence +const extendedTechniqueSchema = techniqueSchema.extend({ + x_org_threat_level: z.enum(['low', 'medium', 'high']), + x_org_last_observed: z.string().datetime() +}); +``` + +## Future Evolution + +### STIX Standards Development + +The library's STIX foundation positions it for future standards evolution: + +- **STIX 2.2**: New relationship types and object properties +- **STIX Extensions**: Formal extension mechanisms beyond custom properties +- **Performance Improvements**: Standards-level optimizations for large datasets + +### ATT&CK Specification Evolution + +STIX provides a stable foundation as ATT&CK specifications evolve: + +- **New Object Types**: STIX patterns accommodate new ATT&CK concepts +- **Relationship Types**: New relationship semantics fit STIX relationship model +- **Version Management**: STIX versioning handles ATT&CK specification updates + +--- + +## Understanding the Foundation's Impact + +The STIX foundation isn't just a data format choice - it's an architectural philosophy that prioritizes: + +1. **Standards compliance** over proprietary optimization +2. **Interoperability** over convenience +3. **Long-term sustainability** over short-term simplicity +4. **Community ecosystem** over isolated solutions + +This philosophy shapes every design decision in the ATT&CK Data Model. Understanding it helps you work effectively with the library and contribute to its evolution. + +**Next**: Explore how this foundation enables **[Schema Design Principles](./schema-design)** that balance STIX compliance with usability. diff --git a/docusaurus/docs/explanation/trade-offs.md b/docusaurus/docs/explanation/trade-offs.md new file mode 100644 index 00000000..1e40a99e --- /dev/null +++ b/docusaurus/docs/explanation/trade-offs.md @@ -0,0 +1,220 @@ +# Architecture and Design Trade-offs + +**Understanding the key decisions and compromises in the ATT&CK Data Model** + +Software architecture involves countless trade-offs where optimizing for one goal requires sacrificing another. +The ATT&CK Data Model makes deliberate architectural choices that prioritize certain benefits while accepting specific costs. +Understanding these trade-offs helps users make informed decisions about when and how to use the library, and explains why certain features work the way they do. + +## Performance vs. Flexibility + +### Choice: Type-Safe Schemas with Runtime Validation + +**Benefits:** + +- Compile-time error detection prevents many bugs before deployment +- IDE support with auto-completion and inline documentation +- Runtime validation ensures data quality and standards compliance +- Clear error messages help developers debug invalid data quickly + +**Costs:** + +- Schema validation adds processing overhead during data loading +- Zod schemas increase bundle size compared to simple type annotations +- Complex nested validation can be slower than basic JSON parsing +- Memory usage is higher due to maintaining both raw and validated data + +**When This Helps:** Applications requiring high data quality, strong type safety, or integration with TypeScript tooling. + +**When This Hurts:** High-performance scenarios processing massive datasets where validation overhead is significant. + +### Alternative Approaches Considered + +**JSON Schema + Type Generation:** + +- Would reduce runtime overhead but lose type-level validation +- Harder to maintain consistency between schemas and TypeScript types +- Less developer-friendly error messages + +**Plain TypeScript Types:** + +- Minimal runtime overhead but no data validation +- Risk of runtime errors from invalid data +- Difficult to ensure STIX 2.1 compliance + +## Memory Usage vs. Query Performance + +### Choice: Relationship Pre-computation and Caching + +**Benefits:** + +- Fast relationship traversal without expensive join operations +- Intuitive API methods like `technique.getTactics()` and `group.getCampaigns()` +- Consistent performance regardless of dataset size +- Enables complex relationship queries without graph database overhead + +**Costs:** + +- Higher memory usage due to storing relationship indexes +- Longer initial loading time as relationships are computed +- Memory usage grows quadratically with highly connected datasets +- Relationship updates require index rebuilding + +**When This Helps:** Interactive applications requiring fast relationship navigation, dashboards, or analysis tools. + +**When This Hurts:** Memory-constrained environments, serverless functions with size limits, or applications only needing simple object access. + +### Alternative Approaches Considered + +**On-Demand Relationship Resolution:** + +- Lower memory usage but much slower relationship queries +- Would require complex caching to maintain reasonable performance +- API inconsistency where some operations are fast and others slow + +**External Graph Database:** + +- Better performance for complex queries but significant infrastructure overhead +- Would require additional deployment complexity +- Loss of type safety in graph traversal operations + +## Standards Compliance vs. Extensibility + +### Choice: STIX 2.1 Foundation with ATT&CK Extensions + +**Benefits:** + +- Full compatibility with existing STIX 2.1 tooling and datasets +- Standards-compliant approach enables ecosystem interoperability +- ATT&CK-specific validations ensure domain model accuracy +- Clear extension points for custom fields and validation rules + +**Costs:** + +- STIX complexity adds learning curve for ATT&CK-only users +- Standards compliance limits certain API design choices +- Verbose object structures due to STIX requirements +- Extension points require careful design to maintain compliance + +**When This Helps:** Organizations with existing STIX infrastructure, tools requiring interoperability, or complex threat intelligence workflows. + +**When This Hurts:** Simple applications only needing basic ATT&CK data access, or scenarios where STIX overhead isn't justified. + +### Alternative Approaches Considered + +**ATT&CK-Native Data Model:** + +- Simpler API and smaller learning curve +- Better performance due to optimized data structures +- Loss of standards compliance and interoperability +- Custom tooling required for data exchange + +**Generic JSON Objects:** + +- Maximum flexibility and minimal constraints +- Loss of type safety, validation, and relationship navigation +- Increased development burden on applications + +## Synchronous vs. Asynchronous API Design + +### Choice: Synchronous Primary API with Async Registration + +**Benefits:** + +- Simple, intuitive API for data access operations +- No async complexity for common use cases like `model.techniques` +- Predictable performance characteristics for queries +- Easy integration with existing synchronous code + +**Costs:** + +- Initial data registration must be async (network/file operations) +- Cannot easily support streaming or progressive data loading +- Memory must hold entire dataset during synchronous operations +- Less suitable for reactive or event-driven architectures + +**When This Helps:** Traditional application architectures, data analysis scripts, or scenarios requiring predictable synchronous data access. + +**When This Hurts:** Event-driven systems, applications requiring progressive data loading, or memory-constrained streaming scenarios. + +## Multi-Domain vs. Single-Domain Optimization + +### Choice: Unified API Supporting Enterprise, Mobile, and ICS + +**Benefits:** + +- Single library handles all ATT&CK domains uniformly +- Consistent API across different domain types +- Easy cross-domain analysis and comparison +- Simplified dependency management for multi-domain applications + +**Costs:** + +- Larger bundle size even for single-domain usage +- Schema complexity must handle domain-specific variations +- Memory overhead for unused domain capabilities +- Potential performance impact from domain abstraction layer + +**When This Helps:** Comprehensive threat analysis, research across multiple domains, or organizations using multiple ATT&CK domains. + +**When This Hurts:** Specialized applications focused on single domain, mobile applications with size constraints, or embedded systems. + +## Error Handling Philosophy + +### Choice: Strict Validation with Relaxed Mode Option + +**Benefits:** + +- Default strict mode ensures data quality and early error detection +- Relaxed mode provides fallback for handling imperfect real-world data +- Clear error messages help developers identify and fix data issues +- Configurable behavior allows different use case optimization + +**Costs:** + +- Strict mode may reject usable data due to minor validation issues +- Error handling complexity increases application development burden +- Relaxed mode can lead to subtle bugs from incomplete data processing +- Debugging can be difficult when validation rules are complex + +**When This Helps:** Production applications requiring data quality assurance, development environments where early error detection is valuable. + +**When This Hurts:** Research scenarios with experimental data, rapid prototyping, or integration with legacy systems producing imperfect data. + +## Development and Maintenance Implications + +### Backward Compatibility vs. Evolution + +We decided to make use of Semantic Versioning with Major Version Breaking Changes + +**Benefits:** Clear compatibility promises and smooth upgrade paths for minor/patch versions. + +**Costs:** Major version upgrades can require significant application changes, slowing adoption of new features. + +## Guidance for Different Use Cases + +### High-Performance Scenarios + +- Consider simpler data access patterns to minimize validation overhead +- Use relaxed parsing mode if data quality requirements permit +- Profile memory usage and consider single-domain usage if appropriate + +### Memory-Constrained Environments + +- Evaluate whether relationship pre-computation benefits justify memory costs +- Consider processing data in smaller batches if possible +- Monitor memory usage patterns and optimize based on actual usage + +### Standards-Compliant Integrations + +- Leverage STIX 2.1 compliance for interoperability benefits +- Use extension points carefully to maintain standards compliance +- Design custom validations to complement rather than replace STIX requirements + +### Rapid Development and Prototyping + +- Use relaxed parsing mode to handle imperfect data gracefully +- Focus on synchronous API patterns for simpler control flow +- Consider single-domain optimization if working with specific domains + +Understanding these trade-offs helps you make informed decisions about whether the ATT&CK Data Model's approach aligns with your specific requirements and constraints. diff --git a/docusaurus/docs/explanation/versioning-philosophy.md b/docusaurus/docs/explanation/versioning-philosophy.md new file mode 100644 index 00000000..df320652 --- /dev/null +++ b/docusaurus/docs/explanation/versioning-philosophy.md @@ -0,0 +1,517 @@ +# Versioning Philosophy + +**Understanding ATT&CK's multi-dimensional versioning approach** + +ATT&CK employs a sophisticated three-dimensional versioning system that tracks changes across different scopes and timeframes. This approach often confuses users who expect simple sequential versioning, but the complexity serves important purposes: enabling independent evolution of standards, specifications, and content while maintaining compatibility across the ecosystem. Understanding this versioning philosophy is crucial for building robust applications and managing long-term compatibility. + +## The Versioning Challenge + +### Multiple Evolution Axes + +ATT&CK data evolves along several independent dimensions, each with different change frequencies, compatibility implications, and governance models: + +#### Standards Evolution (STIX 2.1) + +- **Scope**: Core STIX specification changes +- **Frequency**: Infrequent (years between major versions) +- **Governance**: OASIS standards organization +- **Impact**: Affects all STIX-compliant tools and data + +#### Specification Evolution (ATT&CK Extensions) + +- **Scope**: ATT&CK-specific schema and validation rules +- **Frequency**: Moderate (months to years) +- **Governance**: MITRE ATT&CK team +- **Impact**: Affects ATT&CK data structure and processing logic + +#### Content Evolution (Individual Objects) + +- **Scope**: Individual technique descriptions, relationships, metadata +- **Frequency**: Frequent (weekly to monthly) +- **Governance**: ATT&CK content curators +- **Impact**: Affects analytical accuracy and completeness + +### The Impossibility of Single-Dimension Versioning + +A single version number cannot adequately express changes across these different dimensions: + +**Sequential numbering** (1.0, 2.0, 3.0): + +- ✅ Simple to understand +- ❌ Cannot distinguish between breaking specification changes and minor content updates +- ❌ Forces synchronized releases across independent change streams + +**Date-based versioning** (2023.01, 2023.02): + +- ✅ Clear temporal ordering +- ❌ No semantic information about change impact +- ❌ Doesn't indicate compatibility implications + +**Semantic versioning** (MAJOR.MINOR.PATCH): + +- ✅ Expresses compatibility impact +- ❌ Assumes single change stream +- ❌ Difficult to coordinate across independent governance models + +**Solution**: Use multiple version dimensions, each optimized for tracking changes in its specific domain. + +## The Three Version Dimensions + +### 1. STIX Version (`spec_version`) + +**Format**: `"2.1"` +**Scope**: STIX specification compliance +**Managed by**: OASIS standards organization + +#### Purpose and Characteristics + +**Standards tracking**: Indicates which version of the STIX specification the object conforms to. + +**Parser compatibility**: Enables STIX parsers to determine processing capabilities and requirements. + +**Ecosystem coordination**: Provides a standard reference point for tool compatibility across the threat intelligence ecosystem. + +**Example usage**: + +```json +{ + "spec_version": "2.1", + "type": "attack-pattern", + "id": "attack-pattern--12345..." +} +``` + +#### Evolution Patterns + +**Rare changes**: STIX versions change infrequently (every few years) through formal standardization processes. + +**Breaking changes**: Major STIX version updates may introduce incompatible changes requiring parser updates. + +**Backward compatibility**: STIX generally maintains compatibility within major versions (2.0 → 2.1 was largely additive). + +#### When STIX Version Changes + +**Parser updates required**: Applications must update STIX processing logic for new specification versions. + +**Validation changes**: Schema validation rules may require updates for new STIX features. + +**Ecosystem coordination**: Tool updates typically occur gradually across the threat intelligence ecosystem. + +### 2. ATT&CK Specification Version (`x_mitre_attack_spec_version`) + +**Format**: `"3.3.0"` (semantic versioning) +**Scope**: ATT&CK specification extensions and structure +**Managed by**: MITRE ATT&CK team + +#### Purpose and Characteristics + +**Specification compatibility**: Tracks changes to ATT&CK's extensions of STIX (custom object types, properties, validation rules). + +**Breaking change communication**: Major version increments indicate incompatible changes that may require application updates. + +**Feature availability**: Indicates which ATT&CK features and object types are available in the data. + +**Example usage**: + +```json +{ + "x_mitre_attack_spec_version": "3.3.0", + "type": "x-mitre-detection-strategy", + "x_mitre_analytics": ["analytic-id-1", "analytic-id-2"] +} +``` + +#### Version Semantics + +**Major versions (3.0.0 → 4.0.0)**: + +- Introduce breaking changes to object schemas or validation rules +- May remove deprecated features or object types +- Require application updates for compatibility + +**Minor versions (3.2.0 → 3.3.0)**: + +- Add new object types or properties in backward-compatible ways +- Introduce new features that don't break existing processing +- Applications continue working but may miss new capabilities + +**Patch versions (3.3.0 → 3.3.1)**: + +- Fix bugs in validation rules or schema definitions +- Clarify ambiguous specification language +- Should not affect application compatibility + +#### Recent Specification Evolution + +**Version 3.3.0 changes**: + +- Introduced Detection Strategy framework (analytics, log sources) +- Deprecated legacy data source detection relationships +- Added campaign temporal tracking properties +- Enhanced asset relationship modeling + +**Version 4.0.0 (planned)**: + +- Will remove deprecated data source `detects` relationships +- May introduce additional breaking changes to object schemas + +### 3. Object Version (`x_mitre_version`) + +**Format**: `"1.2"` (major.minor) +**Scope**: Individual object content and metadata +**Managed by**: ATT&CK content curators + +#### Purpose and Characteristics + +**Content tracking**: Indicates when object content (description, metadata, relationships) has been meaningfully updated. + +**Change detection**: Enables applications to identify when objects have been modified since last processing. + +**Analytical currency**: Helps analysts understand the recency and evolution of threat intelligence. + +**Example usage**: + +```json +{ + "x_mitre_version": "1.3", + "name": "Process Injection", + "description": "Updated with new detection guidance...", + "modified": "2023-06-15T10:30:00.000Z" +} +``` + +#### Version Semantics + +**Major increments (1.0 → 2.0)**: + +- Substantial content changes that affect analytical interpretation +- New relationships, significantly updated descriptions, or revised metadata +- May indicate changes in understanding of adversary behavior + +**Minor increments (1.2 → 1.3)**: + +- Incremental content improvements, clarifications, or additions +- Editorial improvements, additional examples, or minor metadata updates +- Generally preserve existing analytical conclusions + +#### Object Version Lifecycle + +```shell +Creation → 1.0 → 1.1 → 1.2 → 2.0 → 2.1 → 3.0... + │ │ │ │ │ │ + │ │ │ │ │ └─ Major content revision + │ │ │ │ └─ Minor improvement + │ │ │ └─ Major analytical update + │ │ └─ Minor content addition + │ └─ First content improvement + └─ Initial creation +``` + +## Version Relationship Patterns + +### Independent Evolution + +The three version dimensions evolve independently, creating complex interaction patterns: + +```json +// Object may have different version update frequencies +{ + "spec_version": "2.1", // Unchanged for years + "x_mitre_attack_spec_version": "3.3.0", // Updated every few months + "x_mitre_version": "2.4", // Updated frequently + "modified": "2023-08-15T14:20:00.000Z" +} +``` + +### Compatibility Matrix + +Different version combinations create compatibility requirements: + +| STIX Version | ATT&CK Spec Version | Compatibility Notes | +|--------------|---------------------|---------------------| +| 2.1 | 3.0.0-3.2.x | Legacy data source model | +| 2.1 | 3.3.0+ | Detection strategy model introduced | +| 2.1 | 4.0.0+ | Legacy data sources removed | + +### Version-Aware Processing + +Applications should adapt behavior based on specification versions: + +```typescript +function processDetectionData(technique: Technique) { + const specVersion = technique.x_mitre_attack_spec_version; + + if (semver.gte(specVersion, '3.3.0')) { + // Use new detection strategy relationships + return technique.getDetectionStrategies(); + } else { + // Fall back to legacy data component relationships + return technique.getDetectedBy(); // Deprecated but still functional + } +} +``` + +## Versioning Trade-offs and Benefits + +### Benefits of Multi-Dimensional Versioning + +#### Independent Evolution + +Different aspects of ATT&CK can evolve at their natural pace without forcing artificial synchronization: + +- **Content updates** can happen frequently as new research emerges +- **Specification changes** can follow careful design and review cycles +- **Standards evolution** can proceed through formal governance processes + +#### Precise Compatibility Signaling + +Applications can make informed decisions about feature support and compatibility: + +```typescript +// Precise compatibility checking +if (semver.gte(object.x_mitre_attack_spec_version, '3.3.0')) { + // Safe to use detection strategy features +} else { + // Must use legacy detection patterns +} +``` + +#### Ecosystem Stability + +Different components of the ATT&CK ecosystem can update independently without breaking other components. + +### Costs of Multi-Dimensional Versioning + +#### Complexity Burden + +Developers must understand and track multiple version dimensions, each with different semantics and update patterns. + +#### Compatibility Matrix Management + +The combination of version dimensions creates a complex compatibility matrix that's difficult to test comprehensively. + +#### Migration Path Complexity + +Upgrading applications may require consideration of multiple version dimensions and their interactions. + +## Version Management Strategies + +### For Application Developers + +#### Version Compatibility Checking + +```typescript +interface VersionRequirements { + minStixVersion?: string; + minAttackSpecVersion?: string; + maxAttackSpecVersion?: string; +} + +function validateCompatibility( + object: AttackObject, + requirements: VersionRequirements +): boolean { + if (requirements.minAttackSpecVersion && + semver.lt(object.x_mitre_attack_spec_version, requirements.minAttackSpecVersion)) { + return false; + } + + if (requirements.maxAttackSpecVersion && + semver.gt(object.x_mitre_attack_spec_version, requirements.maxAttackSpecVersion)) { + return false; + } + + return true; +} +``` + +#### Feature Detection over Version Checking + +```typescript +// ✅ Better: Feature detection +function supportsDetectionStrategies(technique: Technique): boolean { + return typeof technique.getDetectionStrategies === 'function'; +} + +// ❌ Fragile: Version checking +function supportsDetectionStrategies(technique: Technique): boolean { + return semver.gte(technique.x_mitre_attack_spec_version, '3.3.0'); +} +``` + +### For Data Consumers + +#### Change Detection Workflows + +```typescript +// Track object versions to detect updates +interface ObjectVersionCache { + stixId: string; + lastSeenVersion: string; + lastProcessed: Date; +} + +function detectUpdatedObjects( + cache: ObjectVersionCache[], + newData: AttackObject[] +): AttackObject[] { + return newData.filter(obj => { + const cached = cache.find(c => c.stixId === obj.id); + return !cached || cached.lastSeenVersion !== obj.x_mitre_version; + }); +} +``` + +### For Tool Integrators + +#### Graceful Degradation Patterns + +```typescript +// Support multiple specification versions gracefully +class AttackProcessor { + processDetection(technique: Technique) { + // Try modern detection strategy approach + const strategies = this.tryGetDetectionStrategies(technique); + if (strategies.length > 0) { + return this.processDetectionStrategies(strategies); + } + + // Fall back to legacy data component approach + const dataComponents = this.tryGetDataComponents(technique); + if (dataComponents.length > 0) { + return this.processDataComponents(dataComponents); + } + + // Fall back to basic detection text + return this.processDetectionText(technique.x_mitre_detection); + } +} +``` + +## Version Evolution Patterns + +### Predictable Change Cycles + +#### Content Updates (Object Versions) + +- **Frequency**: Weekly to monthly +- **Scope**: Individual object improvements +- **Impact**: Usually additive, may refine analytical conclusions + +#### Specification Updates (ATT&CK Spec Version) + +- **Frequency**: Every 3-6 months +- **Scope**: New object types, properties, or validation rules +- **Impact**: May require application updates for full feature support + +#### Standards Updates (STIX Version) + +- **Frequency**: Every 1-3 years +- **Scope**: Core STIX specification changes +- **Impact**: May require parser and validation logic updates + +### Migration Planning + +#### Specification Version Upgrades + +1. **Review changes**: Understand new features and breaking changes +2. **Test compatibility**: Verify existing code works with new specification +3. **Implement new features**: Add support for new object types or properties +4. **Update validation**: Modify schema validation for new requirements +5. **Plan rollback**: Maintain ability to process older specification versions + +#### Handling Breaking Changes + +```typescript +// Support multiple specification versions during transitions +class VersionAwareProcessor { + processObject(obj: AttackObject) { + switch (obj.x_mitre_attack_spec_version) { + case '3.2.x': + return this.processLegacyFormat(obj); + case '3.3.x': + return this.processCurrentFormat(obj); + case '4.0.x': + return this.processFutureFormat(obj); + default: + throw new UnsupportedVersionError(obj.x_mitre_attack_spec_version); + } + } +} +``` + +## Common Versioning Mistakes + +### 1. Ignoring Specification Versions + +**❌ Problematic**: + +```typescript +// Assuming all objects support modern features +const strategies = technique.getDetectionStrategies(); +``` + +**✅ Correct**: + +```typescript +// Check specification version before using new features +if (semver.gte(technique.x_mitre_attack_spec_version, '3.3.0')) { + const strategies = technique.getDetectionStrategies(); +} +``` + +### 2. Confusing Version Semantics + +**❌ Problematic**: + +```typescript +// Using object version to determine feature availability +if (technique.x_mitre_version >= '2.0') { + // Object version doesn't indicate feature availability +} +``` + +**✅ Correct**: + +```typescript +// Use specification version for feature detection +if (semver.gte(technique.x_mitre_attack_spec_version, '3.3.0')) { + // Specification version indicates feature availability +} +``` + +### 3. Hard-coding Version Requirements + +**❌ Problematic**: + +```typescript +// Brittle version checking +const REQUIRED_SPEC_VERSION = '3.3.0'; +if (obj.x_mitre_attack_spec_version !== REQUIRED_SPEC_VERSION) { + throw new Error('Unsupported version'); +} +``` + +**✅ Better**: + +```typescript +// Flexible version range checking +const MIN_SPEC_VERSION = '3.3.0'; +if (semver.lt(obj.x_mitre_attack_spec_version, MIN_SPEC_VERSION)) { + throw new Error(`Requires spec version ${MIN_SPEC_VERSION} or higher`); +} +``` + +--- + +## The Philosophy in Practice + +ATT&CK's multi-dimensional versioning reflects the reality that complex systems evolve along multiple independent axes. Rather than forcing artificial synchronization that would slow all evolution to the pace of the slowest component, the framework provides precise tracking for each type of change: + +- **STIX versions** track standards compliance +- **ATT&CK specification versions** track feature availability +- **Object versions** track content evolution + +Understanding this philosophy helps you build robust applications that can handle version complexity gracefully and evolve alongside the ATT&CK framework. + +**Next**: Return to the **[Explanation Overview](./)** to explore other aspects of ATT&CK Data Model architecture and design decisions. diff --git a/docusaurus/docs/explanation/why-adm-exists.md b/docusaurus/docs/explanation/why-adm-exists.md new file mode 100644 index 00000000..5cb0a7d8 --- /dev/null +++ b/docusaurus/docs/explanation/why-adm-exists.md @@ -0,0 +1,298 @@ +# Why the ATT&CK Data Model Exists + +**The problem context, solution approach, and value proposition** + +The ATT&CK Data Model library didn't emerge in a vacuum - it was created to solve specific, recurring problems that security teams and researchers face when working with MITRE ATT&CK data. Understanding these problems and the solution approach helps clarify when and why to use this library. + +## The Problem Context + +### ATT&CK Data Complexity + +MITRE ATT&CK, while invaluable for threat intelligence and security operations, presents several challenges when used programmatically: + +**Raw Data Challenges**: + +- ATT&CK data is distributed as STIX 2.1 JSON bundles containing hundreds of objects +- Relationships between objects are expressed through separate relationship objects, not direct references +- Objects have complex validation rules that aren't immediately obvious from the JSON structure +- Different ATT&CK domains (Enterprise, Mobile, ICS) have subtle but important differences + +**Developer Experience Challenges**: + +- No built-in type safety - easy to access non-existent properties or use wrong data types +- Relationship navigation requires manual lookup and cross-referencing +- Validation errors are cryptic and don't point to specific ATT&CK requirements +- No clear patterns for common operations like "find all techniques used by a group" + +### Before the ATT&CK Data Model + +Prior to this library, developers typically handled ATT&CK data in one of these ways: + +#### 1. Direct JSON Manipulation + +```javascript +// Raw approach - fragile and error-prone +const attackData = JSON.parse(fs.readFileSync('enterprise-attack.json')); +const techniques = attackData.objects.filter(obj => obj.type === 'attack-pattern'); + +// Find tactics for a technique - manual relationship lookup +const technique = techniques.find(t => t.external_references[0].external_id === 'T1055'); +const tacticRelationships = attackData.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'uses' +); +const tactics = tacticRelationships.map(rel => + attackData.objects.find(obj => obj.id === rel.target_ref) +); +``` + +**Problems**: + +- No type safety +- Manual relationship traversal +- No validation +- Fragile to data structure changes + +#### 2. Custom Wrapper Classes + +```javascript +// Custom approach - reinventing the wheel +class AttackDataParser { + constructor(jsonData) { + this.techniques = jsonData.objects.filter(obj => obj.type === 'attack-pattern'); + this.tactics = jsonData.objects.filter(obj => obj.type === 'x-mitre-tactic'); + // ... more filtering and indexing + } + + getTacticsForTechnique(techniqueId) { + // Custom relationship logic + // ... dozens of lines of relationship traversal + } +} +``` + +**Problems**: + +- Reinventing relationship logic +- Inconsistent validation approaches +- No standard patterns across teams +- Maintenance burden for each team + +#### 3. Generic STIX Libraries + +```javascript +// Generic STIX approach - doesn't understand ATT&CK semantics +const stixParser = new STIXParser(); +const bundle = stixParser.parse(attackData); + +// Generic STIX relationships don't understand ATT&CK-specific patterns +const relationships = bundle.relationships.filter(/* ... */); +``` + +**Problems**: + +- No ATT&CK-specific validation +- Missing ATT&CK domain knowledge +- No awareness of ATT&CK conventions +- Generic tools don't optimize for ATT&CK use cases + +### The Core Problems + +Through observing these patterns across security teams, several core problems emerged: + +1. **Repetitive Boilerplate**: Every team reimplemented the same relationship navigation logic +2. **Validation Gaps**: Data quality issues only discovered at runtime, often in production +3. **Type Safety Absence**: No compile-time guarantees about data structure +4. **Knowledge Duplication**: ATT&CK domain knowledge scattered across individual implementations +5. **Version Compatibility**: Difficulty updating to new ATT&CK versions +6. **Testing Challenges**: Hard to test applications against different ATT&CK datasets + +## The Solution Approach + +### Design Philosophy + +The ATT&CK Data Model was designed with a specific philosophy: + +**"Provide a type-safe, relationship-aware, standards-compliant interface to ATT&CK data that eliminates boilerplate while preserving flexibility."** + +This philosophy drives several key decisions: + +#### Standards Compliance First + +Rather than creating a new data format, the library builds on STIX 2.1 compliance. This ensures: + +- Interoperability with existing STIX tools +- Long-term compatibility as standards evolve +- No vendor lock-in - data remains in standard formats + +#### Type Safety Without Performance Cost + +TypeScript integration provides compile-time safety while Zod schemas enable runtime validation. This combination eliminates entire classes of bugs while maintaining performance. + +#### Relationship Navigation as First-Class Feature + +Instead of treating relationships as secondary, the library makes relationship traversal the primary interface. Methods like `technique.getTactics()` abstract away the complexity of STIX relationship objects. + +### Architecture Decisions + +#### Three-Layer Design + +1. **Schema Layer**: Zod schemas provide validation and TypeScript type inference +2. **Class Layer**: Implementation classes add relationship navigation methods +3. **Data Layer**: Flexible data source architecture supports multiple ATT&CK distributions + +This separation allows using only the layers you need - schemas for validation, classes for navigation, or the complete stack for full functionality. + +#### Immutable Data Model + +ATT&CK data represents shared threat intelligence that shouldn't be accidentally modified. Immutability prevents bugs while enabling safe concurrent access. + +#### Extensibility Through Standards + +Custom fields and extensions follow STIX conventions, ensuring that customizations remain interoperable with other STIX tools. + +## Value Proposition + +### For Security Engineers + +**Before**: "I spend hours writing relationship traversal code and debugging data format issues." + +**After**: "I focus on security logic while the library handles data complexity." + +```typescript +// Before - manual relationship lookup +const tacticsForTechnique = attackData.objects + .filter(obj => obj.type === 'relationship' && obj.source_ref === techniqueId) + .map(rel => attackData.objects.find(obj => obj.id === rel.target_ref)); + +// After - simple method call +const tactics = technique.getTactics(); +``` + +### For Application Developers + +**Before**: "I'm not sure if my code will break when ATT&CK releases new data." + +**After**: "Type safety and validation catch issues at build time, not runtime." + +```typescript +// TypeScript catches these errors at compile time +const technique = attackDataModel.techniques[0]; +technique.nonExistentProperty; // ❌ Compile error +technique.name.toNumber(); // ❌ Compile error - name is string +technique.name.toUpperCase(); // ✅ Works - IntelliSense shows available methods +``` + +### For Research Teams + +**Before**: "Each researcher implements their own ATT&CK data parsing, leading to inconsistent results." + +**After**: "Standardized data access ensures reproducible research across team members." + +```typescript +// Consistent API across different research projects +const credentialAccessTechniques = attackDataModel.techniques.filter(technique => + technique.getTactics().some(tactic => tactic.name === 'Credential Access') +); +``` + +### For Enterprise Security Teams + +**Before**: "We can't confidently deploy ATT&CK-based tools because data validation is unreliable." + +**After**: "Strict validation and type safety enable confident production deployments." + +```typescript +// Validation ensures data meets all ATT&CK requirements +try { + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); // Guaranteed valid data +} catch (ValidationError) { + // Handle invalid data before it reaches production +} +``` + +## When to Use This Library + +### Strong Fit Scenarios + +The ATT&CK Data Model is particularly valuable when you: + +- **Build applications** that work with ATT&CK data regularly +- **Need reliability** in production systems processing threat intelligence +- **Want type safety** to catch errors at development time +- **Work across teams** and need consistent ATT&CK data access patterns +- **Update frequently** to new ATT&CK versions and need migration confidence +- **Integrate with STIX tools** while maintaining ATT&CK-specific functionality + +### Alternative Approaches + +The library might not be the best fit if you: + +- **Need minimal dependencies** and are comfortable with manual JSON processing +- **Use non-STIX ATT&CK distributions** exclusively +- **Only need one-time data analysis** rather than ongoing application development +- **Work primarily with non-ATT&CK threat intelligence** frameworks + +## Impact on the Ecosystem + +### Standardization Benefits + +By providing a common interface to ATT&CK data, the library enables: + +- **Consistent tooling** across different organizations +- **Shareable code patterns** for common ATT&CK operations +- **Reduced learning curve** for new team members +- **Improved integration** between different security tools + +### Community Development + +The library serves as a foundation for: + +- **Specialized ATT&CK tools** that build on the data model +- **Educational resources** that can assume a common data interface +- **Research reproducibility** through standardized data access +- **Best practices** for working with structured threat intelligence + +## Future Evolution + +### Adaptation to Standards + +As STIX and ATT&CK specifications evolve, the library serves as an adaptation layer: + +- **Version compatibility** handling across ATT&CK releases +- **Standards updates** integrated transparently for users +- **Deprecation management** with clear migration paths +- **Extension points** for emerging threat intelligence standards + +### Ecosystem Growth + +The library enables ecosystem development through: + +- **Plugin architectures** for custom data sources +- **Extension patterns** for organization-specific additions +- **Integration points** with security orchestration platforms +- **API stability** that enables long-term tool development + +## Measuring Success + +The ATT&CK Data Model's success is measured by: + +- **Reduced development time** for ATT&CK-based applications +- **Fewer runtime errors** related to data handling +- **Increased adoption** of ATT&CK in security tools +- **Community contributions** that extend the library's capabilities +- **Industry standardization** around common ATT&CK data patterns + +--- + +## Next Steps in Understanding + +Now that you understand why the ATT&CK Data Model exists, explore how it achieves these goals: + +- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX compliance drives architectural decisions +- **[Schema Design Principles](./schema-design)** - How validation and type safety are implemented +- **[Performance vs Flexibility Trade-offs](./trade-offs)** - The balance between speed and usability + +The problem context shapes every design decision in the library. Understanding this context helps you use the library effectively and contribute to its continued evolution. diff --git a/docusaurus/docs/how-to-guides/error-handling.md b/docusaurus/docs/how-to-guides/error-handling.md new file mode 100644 index 00000000..00018cb7 --- /dev/null +++ b/docusaurus/docs/how-to-guides/error-handling.md @@ -0,0 +1,704 @@ +# How to Handle Parsing Errors Gracefully + +**Implement robust error handling for production applications** + +When working with ATT&CK data in production, you need comprehensive error handling to manage data source failures, validation errors, and parsing issues. This guide shows you how to build resilient applications that handle errors gracefully while providing meaningful feedback. + +## Problem + +You need to handle various error scenarios: + +- Network failures when loading remote data sources +- Invalid STIX data that fails validation +- Missing or corrupted data files +- Partial parsing failures in relaxed mode +- Timeout issues with large datasets + +## Solution Overview + +Implement layered error handling with specific strategies for different error types, comprehensive logging, and graceful degradation. + +## Step 1: Understanding Error Types + +The ATT&CK Data Model can throw several types of errors: + +```typescript +import { + registerDataSource, + loadDataModel, + DataSource, + DataSourceError, + ValidationError +} from '@mitre-attack/attack-data-model'; +import { ZodError } from 'zod'; + +// Error types you'll encounter: +// 1. Network/IO errors - failed downloads, missing files +// 2. Zod validation errors - schema validation failures +// 3. Data source errors - configuration issues +// 4. Parsing errors - malformed JSON, invalid STIX +// 5. Registration errors - data source registration failures +``` + +## Step 2: Basic Error Handling Pattern + +Create a robust data loading function: + +```typescript +import fs from 'fs'; +import { setTimeout } from 'timers/promises'; + +interface LoadResult { + success: boolean; + dataModel?: AttackDataModel; + errors: string[]; + warnings: string[]; +} + +async function loadAttackDataSafely( + source: 'attack' | 'file' | 'url', + options: any +): Promise { + const result: LoadResult = { + success: false, + errors: [], + warnings: [] + }; + + try { + // Step 1: Create data source with validation + console.log(`📡 Loading ATT&CK data from ${source}...`); + + const dataSource = new DataSource({ + source, + parsingMode: 'relaxed', // More forgiving for error scenarios + ...options + }); + + // Step 2: Register with timeout + const uuid = await Promise.race([ + registerDataSource(dataSource), + setTimeout(30000).then(() => { + throw new Error('Registration timeout after 30 seconds'); + }) + ]); + + if (!uuid) { + result.errors.push('Failed to register data source - no UUID returned'); + return result; + } + + console.log('✅ Data source registered successfully'); + + // Step 3: Load data model + const dataModel = loadDataModel(uuid); + + // Step 4: Validate data completeness + const validation = validateDataCompleteness(dataModel); + result.warnings.push(...validation.warnings); + + if (validation.criticalIssues.length > 0) { + result.errors.push(...validation.criticalIssues); + return result; + } + + result.success = true; + result.dataModel = dataModel; + + console.log(`✅ Successfully loaded ${dataModel.techniques.length} techniques`); + + } catch (error) { + result.errors.push(handleLoadingError(error)); + } + + return result; +} + +function handleLoadingError(error: unknown): string { + if (error instanceof ZodError) { + return `Validation error: ${error.errors.map(e => + `${e.path.join('.')}: ${e.message}` + ).join(', ')}`; + } + + if (error instanceof Error) { + // Network/timeout errors + if (error.message.includes('timeout')) { + return 'Request timeout - data source may be slow or unavailable'; + } + + // File system errors + if (error.message.includes('ENOENT')) { + return 'File not found - check file path and permissions'; + } + + // Network errors + if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) { + return 'Network error - check internet connection and URL'; + } + + return `Error: ${error.message}`; + } + + return `Unknown error: ${String(error)}`; +} +``` + +## Step 3: Data Completeness Validation + +Validate that loaded data meets minimum requirements: + +```typescript +interface DataValidation { + warnings: string[]; + criticalIssues: string[]; + stats: { + techniques: number; + tactics: number; + groups: number; + software: number; + }; +} + +function validateDataCompleteness(dataModel: AttackDataModel): DataValidation { + const result: DataValidation = { + warnings: [], + criticalIssues: [], + stats: { + techniques: dataModel.techniques.length, + tactics: dataModel.tactics.length, + groups: dataModel.groups.length, + software: dataModel.malware.length + dataModel.tools.length + } + }; + + // Critical issues - data is unusable + if (result.stats.techniques === 0) { + result.criticalIssues.push('No techniques loaded - data source may be invalid'); + } + + if (result.stats.tactics === 0) { + result.criticalIssues.push('No tactics loaded - core ATT&CK structure missing'); + } + + // Warnings - data is usable but incomplete + if (result.stats.techniques < 100) { + result.warnings.push(`Only ${result.stats.techniques} techniques loaded - expected 150+`); + } + + if (result.stats.groups < 50) { + result.warnings.push(`Only ${result.stats.groups} groups loaded - expected 100+`); + } + + if (result.stats.software < 200) { + result.warnings.push(`Only ${result.stats.software} software items loaded - expected 400+`); + } + + // Check relationship integrity + const brokenRelationships = checkRelationshipIntegrity(dataModel); + if (brokenRelationships.length > 0) { + result.warnings.push(`${brokenRelationships.length} broken relationships found`); + } + + return result; +} + +function checkRelationshipIntegrity(dataModel: AttackDataModel): string[] { + const issues: string[] = []; + + // Check technique-tactic relationships + dataModel.techniques.forEach(technique => { + try { + const tactics = technique.getTactics(); + if (tactics.length === 0) { + issues.push(`Technique ${technique.name} has no associated tactics`); + } + } catch (error) { + issues.push(`Error getting tactics for technique ${technique.name}: ${error}`); + } + }); + + return issues.slice(0, 10); // Limit to first 10 issues +} +``` + +## Step 4: Retry Logic with Exponential Backoff + +Implement retry logic for transient failures: + +```typescript +interface RetryOptions { + maxAttempts: number; + baseDelay: number; + maxDelay: number; + backoffMultiplier: number; +} + +async function loadWithRetry( + loadFunction: () => Promise, + options: RetryOptions = { + maxAttempts: 3, + baseDelay: 1000, + maxDelay: 10000, + backoffMultiplier: 2 + } +): Promise { + let lastResult: LoadResult | null = null; + + for (let attempt = 1; attempt <= options.maxAttempts; attempt++) { + console.log(`📡 Attempt ${attempt}/${options.maxAttempts}`); + + try { + const result = await loadFunction(); + + if (result.success) { + if (attempt > 1) { + console.log(`✅ Succeeded on attempt ${attempt}`); + } + return result; + } + + lastResult = result; + + // Don't retry for certain error types + const nonRetryableErrors = [ + 'File not found', + 'Validation error', + 'Invalid configuration' + ]; + + const hasNonRetryableError = result.errors.some(error => + nonRetryableErrors.some(pattern => error.includes(pattern)) + ); + + if (hasNonRetryableError) { + console.log('❌ Non-retryable error detected, aborting retries'); + break; + } + + } catch (error) { + lastResult = { + success: false, + errors: [`Attempt ${attempt} failed: ${error}`], + warnings: [] + }; + } + + // Calculate delay with exponential backoff + if (attempt < options.maxAttempts) { + const delay = Math.min( + options.baseDelay * Math.pow(options.backoffMultiplier, attempt - 1), + options.maxDelay + ); + + console.log(`⏳ Waiting ${delay}ms before retry...`); + await setTimeout(delay); + } + } + + console.log(`❌ All ${options.maxAttempts} attempts failed`); + return lastResult || { + success: false, + errors: ['All retry attempts failed'], + warnings: [] + }; +} +``` + +## Step 5: Fallback Data Sources + +Implement fallback strategies when primary sources fail: + +```typescript +interface DataSourceConfig { + primary: DataSource; + fallbacks: DataSource[]; + cacheOptions?: { + enabled: boolean; + ttl: number; // Time to live in seconds + path: string; + }; +} + +class RobustAttackLoader { + private cache = new Map(); + + async loadWithFallbacks(config: DataSourceConfig): Promise { + const sources = [config.primary, ...config.fallbacks]; + let lastResult: LoadResult | null = null; + + // Try cache first if enabled + if (config.cacheOptions?.enabled) { + const cached = this.tryLoadFromCache(config.cacheOptions.path, config.cacheOptions.ttl); + if (cached.success) { + console.log('✅ Loaded data from cache'); + return cached; + } + } + + // Try each data source + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + const isLastSource = i === sources.length - 1; + + console.log(`🎯 Trying data source ${i + 1}/${sources.length}`); + + const result = await loadWithRetry(async () => { + return await loadAttackDataSafely( + source.source as any, + source + ); + }); + + if (result.success && result.dataModel) { + console.log(`✅ Successfully loaded from data source ${i + 1}`); + + // Cache successful result + if (config.cacheOptions?.enabled) { + await this.saveToCache(result.dataModel, config.cacheOptions.path); + } + + return result; + } + + lastResult = result; + + if (!isLastSource) { + console.log(`❌ Data source ${i + 1} failed, trying next...`); + } + } + + console.log('❌ All data sources failed'); + return lastResult || { + success: false, + errors: ['All data sources failed'], + warnings: [] + }; + } + + private tryLoadFromCache(cachePath: string, ttl: number): LoadResult { + try { + if (!fs.existsSync(cachePath)) { + return { success: false, errors: ['Cache file not found'], warnings: [] }; + } + + const stats = fs.statSync(cachePath); + const age = (Date.now() - stats.mtime.getTime()) / 1000; + + if (age > ttl) { + return { success: false, errors: ['Cache expired'], warnings: [] }; + } + + const cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8')); + + // Reconstruct AttackDataModel from cached data + // (This would need custom deserialization logic) + + return { + success: true, + errors: [], + warnings: [`Using cached data (${Math.round(age)}s old)`] + }; + + } catch (error) { + return { + success: false, + errors: [`Cache load failed: ${error}`], + warnings: [] + }; + } + } + + private async saveToCache(dataModel: AttackDataModel, cachePath: string): Promise { + try { + // Create cache directory if it doesn't exist + const cacheDir = path.dirname(cachePath); + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + // Serialize data model + const cacheData = { + timestamp: Date.now(), + techniques: dataModel.techniques, + tactics: dataModel.tactics, + groups: dataModel.groups, + // ... other collections + }; + + fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2)); + console.log(`💾 Data cached to ${cachePath}`); + + } catch (error) { + console.warn(`⚠️ Failed to save cache: ${error}`); + } + } +} +``` + +## Step 6: Error Reporting and Monitoring + +Implement comprehensive error reporting: + +```typescript +interface ErrorReport { + timestamp: string; + source: string; + errorType: string; + message: string; + context: any; + stackTrace?: string; +} + +class ErrorReporter { + private errors: ErrorReport[] = []; + + reportError(source: string, error: unknown, context: any = {}): void { + const report: ErrorReport = { + timestamp: new Date().toISOString(), + source, + errorType: error instanceof Error ? error.constructor.name : 'Unknown', + message: error instanceof Error ? error.message : String(error), + context, + stackTrace: error instanceof Error ? error.stack : undefined + }; + + this.errors.push(report); + + // Log to console + console.error(`❌ [${source}] ${report.message}`, report.context); + + // Send to monitoring service (implement based on your needs) + this.sendToMonitoring(report); + } + + private sendToMonitoring(report: ErrorReport): void { + // Example: Send to external monitoring service + // fetch('/api/errors', { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify(report) + // }); + } + + getErrorSummary(): { [key: string]: number } { + const summary: { [key: string]: number } = {}; + + this.errors.forEach(error => { + summary[error.errorType] = (summary[error.errorType] || 0) + 1; + }); + + return summary; + } + + getRecentErrors(hours: number = 24): ErrorReport[] { + const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000); + return this.errors.filter(error => new Date(error.timestamp) > cutoff); + } +} + +// Global error reporter instance +const errorReporter = new ErrorReporter(); +``` + +## Step 7: Application-Level Error Handling + +Create a complete error handling solution: + +```typescript +interface AppConfig { + dataSources: DataSourceConfig; + errorHandling: { + maxRetries: number; + enableCache: boolean; + cacheTtl: number; + failFast: boolean; + }; + monitoring: { + enabled: boolean; + endpoint?: string; + }; +} + +class AttackDataApp { + private config: AppConfig; + private loader: RobustAttackLoader; + private dataModel?: AttackDataModel; + + constructor(config: AppConfig) { + this.config = config; + this.loader = new RobustAttackLoader(); + } + + async initialize(): Promise { + try { + console.log('🚀 Initializing ATT&CK Data Application...'); + + const result = await this.loader.loadWithFallbacks(this.config.dataSources); + + if (!result.success) { + const errorMsg = `Failed to initialize: ${result.errors.join(', ')}`; + errorReporter.reportError('App.initialize', new Error(errorMsg)); + + if (this.config.errorHandling.failFast) { + throw new Error(errorMsg); + } + + console.warn('⚠️ Running with degraded functionality'); + return; + } + + this.dataModel = result.dataModel; + + // Report warnings but continue + if (result.warnings.length > 0) { + result.warnings.forEach(warning => { + console.warn(`⚠️ ${warning}`); + }); + } + + console.log('✅ Application initialized successfully'); + + } catch (error) { + errorReporter.reportError('App.initialize', error); + throw error; + } + } + + // Safe method calls with error handling + async getTechnique(id: string): Promise { + try { + if (!this.dataModel) { + throw new Error('Application not initialized'); + } + + const technique = this.dataModel.techniques.find(t => + t.external_references[0].external_id === id + ); + + return technique || null; + + } catch (error) { + errorReporter.reportError('App.getTechnique', error, { id }); + return null; + } + } + + // Health check for monitoring + getHealthStatus(): { + status: 'healthy' | 'degraded' | 'unhealthy'; + details: any; + } { + const recentErrors = errorReporter.getRecentErrors(1); + const errorCount = recentErrors.length; + + if (!this.dataModel) { + return { + status: 'unhealthy', + details: { + message: 'Data model not loaded', + errors: errorCount + } + }; + } + + if (errorCount > 10) { + return { + status: 'degraded', + details: { + message: `High error rate: ${errorCount} errors in last hour`, + techniques: this.dataModel.techniques.length + } + }; + } + + return { + status: 'healthy', + details: { + techniques: this.dataModel.techniques.length, + tactics: this.dataModel.tactics.length, + uptime: process.uptime() + } + }; + } +} +``` + +## Step 8: Usage Example + +Put it all together in a production-ready application: + +```typescript +async function main() { + const config: AppConfig = { + dataSources: { + primary: new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' + }), + fallbacks: [ + new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' // More forgiving fallback + }), + new DataSource({ + source: 'file', + file: './backup/enterprise-attack.json', + parsingMode: 'relaxed' + }) + ], + cacheOptions: { + enabled: true, + ttl: 3600, // 1 hour + path: './cache/attack-data.json' + } + }, + errorHandling: { + maxRetries: 3, + enableCache: true, + cacheTtl: 3600, + failFast: false + }, + monitoring: { + enabled: true + } + }; + + const app = new AttackDataApp(config); + + try { + await app.initialize(); + + // Your application logic here + const technique = await app.getTechnique('T1055'); + if (technique) { + console.log(`Found technique: ${technique.name}`); + } + + // Monitor health + setInterval(() => { + const health = app.getHealthStatus(); + console.log(`Health: ${health.status}`, health.details); + }, 60000); // Check every minute + + } catch (error) { + console.error('❌ Application failed to start:', error); + process.exit(1); + } +} + +main().catch(console.error); +``` + +--- + +## Next Steps + +- **Bundle Validation**: Learn how to [validate custom STIX bundles](./validate-bundles) before processing +- **Schema Extension**: See how to [extend schemas with custom fields](./extend-schemas) for enhanced error detection +- **Reference**: Check the [complete API documentation](../reference/) for error handling methods + +Your application now handles errors gracefully and provides reliable ATT&CK data access even in challenging environments! diff --git a/docusaurus/docs/how-to-guides/extend-schemas.md b/docusaurus/docs/how-to-guides/extend-schemas.md new file mode 100644 index 00000000..3e7f92ed --- /dev/null +++ b/docusaurus/docs/how-to-guides/extend-schemas.md @@ -0,0 +1,441 @@ +# How to Extend Schemas with Custom Fields + +**Add your own properties to ATT&CK objects while preserving validation** + +When building applications with the ATT&CK Data Model, you may need to add custom fields to ATT&CK objects for your specific use case. This guide shows you how to extend the provided schemas while maintaining all existing validation rules. + +## Problem + +You want to: + +- Add custom fields to ATT&CK objects (techniques, groups, etc.) +- Preserve all existing ATT&CK validation rules and refinements +- Use the extended schemas in your application +- Maintain type safety with TypeScript + +## Solution Overview + +Extend ATT&CK schemas using Zod's extension methods while manually reapplying the original refinements that would otherwise be lost. + +## Step 1: Understanding Schema Refinements + +ATT&CK schemas use Zod refinements for advanced validation. When you extend a schema, these refinements are discarded: + +```typescript +import { campaignSchema } from '@mitre-attack/attack-data-model'; + +// ❌ This loses the original refinements +const extendedCampaign = campaignSchema.extend({ + customField: z.string() +}); + +// ✅ This preserves refinements (we'll show how below) +``` + +First, check which refinements a schema uses by examining its source file or the exported refinement functions. + +## Step 2: Extending Campaigns with Custom Fields + +Let's extend the campaign schema with custom tracking fields: + +```typescript +import { + campaignSchema, + createFirstAliasRefinement, + createCitationsRefinement +} from '@mitre-attack/attack-data-model'; +import { z } from 'zod'; + +// Define your custom fields +const customCampaignFields = z.object({ + x_custom_severity: z.enum(['low', 'medium', 'high', 'critical']).optional(), + x_custom_industry_targets: z.array(z.string()).optional(), + x_custom_confidence_score: z.number().min(0).max(100).optional(), + x_custom_tracking_id: z.string().optional() +}); + +// Extend the schema with custom fields +const extendedCampaignSchema = campaignSchema + .extend(customCampaignFields.shape) + .superRefine((data, ctx) => { + // Reapply the original refinements + createFirstAliasRefinement()(data, ctx); + createCitationsRefinement()(data, ctx); + + // Add custom validation rules + if (data.x_custom_confidence_score !== undefined && + data.x_custom_confidence_score > 50 && + !data.x_custom_severity) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "High confidence campaigns must specify severity", + path: ['x_custom_severity'] + }); + } + }); + +// Create TypeScript type from extended schema +type ExtendedCampaign = z.infer; +``` + +## Step 3: Extending Techniques with Metadata + +Extend technique schemas for custom threat intelligence: + +```typescript +import { + techniqueSchema, + createTacticRefinement, + createAttackIdRefinement, + createCitationsRefinement +} from '@mitre-attack/attack-data-model'; + +const customTechniqueFields = z.object({ + x_custom_detection_difficulty: z.enum(['easy', 'medium', 'hard']).optional(), + x_custom_business_impact: z.number().min(1).max(5).optional(), + x_custom_last_seen: z.string().datetime().optional(), + x_custom_tags: z.array(z.string()).optional(), + x_custom_iocs: z.array(z.object({ + type: z.enum(['hash', 'ip', 'domain', 'file_path']), + value: z.string(), + confidence: z.number().min(0).max(100) + })).optional() +}); + +const extendedTechniqueSchema = techniqueSchema + .extend(customTechniqueFields.shape) + .superRefine((data, ctx) => { + // Reapply original refinements + createTacticRefinement()(data, ctx); + createAttackIdRefinement()(data, ctx); + createCitationsRefinement()(data, ctx); + + // Custom validation: ensure IoCs have valid formats + if (data.x_custom_iocs) { + data.x_custom_iocs.forEach((ioc, index) => { + if (ioc.type === 'ip' && !isValidIP(ioc.value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid IP address format", + path: ['x_custom_iocs', index, 'value'] + }); + } + }); + } + }); + +type ExtendedTechnique = z.infer; + +// Helper validation function +function isValidIP(ip: string): boolean { + const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; + const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; + return ipv4Regex.test(ip) || ipv6Regex.test(ip); +} +``` + +## Step 4: Creating a Schema Extension Factory + +Build a reusable factory for extending any ATT&CK schema: + +```typescript +interface SchemaExtensionConfig { + customFields: z.ZodRawShape; + refinements: Array<(data: any, ctx: z.RefinementCtx) => void>; + customValidation?: (data: T, ctx: z.RefinementCtx) => void; +} + +function extendAttackSchema( + baseSchema: z.ZodObject, + config: SchemaExtensionConfig +) { + return baseSchema + .extend(config.customFields) + .superRefine((data, ctx) => { + // Apply original refinements + config.refinements.forEach(refinement => { + refinement(data, ctx); + }); + + // Apply custom validation if provided + if (config.customValidation) { + config.customValidation(data as U, ctx); + } + }); +} + +// Usage examples +const extendedGroupSchema = extendAttackSchema(groupSchema, { + customFields: { + x_custom_threat_level: z.enum(['nation-state', 'criminal', 'hacktivist']).optional(), + x_custom_active_since: z.string().datetime().optional() + }, + refinements: [ + createFirstAliasRefinement(), + createCitationsRefinement() + ], + customValidation: (data, ctx) => { + if (data.x_custom_threat_level === 'nation-state' && + !data.x_custom_active_since) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Nation-state groups must have active_since date", + path: ['x_custom_active_since'] + }); + } + } +}); +``` + +## Step 5: Working with Extended Schemas + +Use your extended schemas in applications: + +```typescript +// Validate data with extended schema +function validateExtendedCampaign(data: unknown): ExtendedCampaign { + try { + return extendedCampaignSchema.parse(data); + } catch (error) { + if (error instanceof z.ZodError) { + console.error('Validation errors:'); + error.errors.forEach(err => { + console.error(`${err.path.join('.')}: ${err.message}`); + }); + } + throw error; + } +} + +// Example usage with custom data +const customCampaignData = { + // Standard ATT&CK fields + type: "campaign", + spec_version: "2.1", + id: "campaign--12345678-1234-1234-1234-123456789012", + created: "2023-01-01T00:00:00.000Z", + modified: "2023-01-01T00:00:00.000Z", + name: "Custom Campaign", + description: "A campaign with custom fields", + x_mitre_attack_spec_version: "3.3.0", + x_mitre_version: "1.0", + + // Your custom fields + x_custom_severity: "high", + x_custom_industry_targets: ["finance", "healthcare"], + x_custom_confidence_score: 85, + x_custom_tracking_id: "TRACK-2023-001" +}; + +const validatedCampaign = validateExtendedCampaign(customCampaignData); +console.log(validatedCampaign.x_custom_severity); // TypeScript knows this exists! +``` + +## Step 6: Creating Custom Implementation Classes + +Extend the implementation classes to work with your custom fields: + +```typescript +import { CampaignImpl } from '@mitre-attack/attack-data-model'; + +class ExtendedCampaignImpl extends CampaignImpl { + // Custom methods for your extended fields + getSeverityLevel(): string { + return (this as any).x_custom_severity || 'unknown'; + } + + getIndustryTargets(): string[] { + return (this as any).x_custom_industry_targets || []; + } + + isHighConfidence(): boolean { + const score = (this as any).x_custom_confidence_score; + return score !== undefined && score >= 80; + } + + // Custom business logic + calculateRiskScore(): number { + const severityWeight = { + 'low': 1, + 'medium': 2, + 'high': 3, + 'critical': 4 + }; + + const severity = this.getSeverityLevel(); + const confidence = (this as any).x_custom_confidence_score || 0; + + return (severityWeight[severity] || 0) * (confidence / 100); + } +} + +// Factory function to create extended instances +function createExtendedCampaign(data: ExtendedCampaign): ExtendedCampaignImpl { + return new ExtendedCampaignImpl(data); +} +``` + +## Step 7: Integrating with Data Sources + +Load custom data with extended schemas: + +```typescript +import { DataSource } from '@mitre-attack/attack-data-model'; + +class ExtendedDataProcessor { + async processCustomBundle(filePath: string) { + // Load the bundle with standard data source + const dataSource = new DataSource({ + source: 'file', + file: filePath, + parsingMode: 'relaxed' // Allow custom fields + }); + + const uuid = await registerDataSource(dataSource); + const attackDataModel = loadDataModel(uuid); + + // Process campaigns with extended validation + const extendedCampaigns = attackDataModel.campaigns.map(campaign => { + try { + // Validate against extended schema + const validatedData = extendedCampaignSchema.parse(campaign); + return createExtendedCampaign(validatedData); + } catch (error) { + console.warn(`Campaign ${campaign.name} failed extended validation:`, error); + return null; + } + }).filter(Boolean) as ExtendedCampaignImpl[]; + + return extendedCampaigns; + } +} +``` + +## Step 8: Schema Composition for Complex Extensions + +Combine multiple extensions for comprehensive customization: + +```typescript +// Base threat intelligence fields +const threatIntelFields = z.object({ + x_custom_confidence: z.number().min(0).max(100), + x_custom_source: z.string(), + x_custom_last_updated: z.string().datetime() +}); + +// Organization-specific fields +const orgSpecificFields = z.object({ + x_org_priority: z.enum(['p1', 'p2', 'p3', 'p4']), + x_org_assigned_analyst: z.string().optional(), + x_org_notes: z.array(z.object({ + timestamp: z.string().datetime(), + author: z.string(), + content: z.string() + })).optional() +}); + +// Compose multiple extensions +const fullyExtendedTechniqueSchema = techniqueSchema + .extend({ + ...threatIntelFields.shape, + ...orgSpecificFields.shape, + ...customTechniqueFields.shape + }) + .superRefine((data, ctx) => { + // Apply all refinements and validations + createTacticRefinement()(data, ctx); + createAttackIdRefinement()(data, ctx); + createCitationsRefinement()(data, ctx); + + // Cross-field validation + if (data.x_org_priority === 'p1' && !data.x_org_assigned_analyst) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "P1 techniques must have assigned analyst", + path: ['x_org_assigned_analyst'] + }); + } + }); + +type FullyExtendedTechnique = z.infer; +``` + +## Common Pitfalls + +### Forgetting Refinements + +Always check the original schema file to see which refinements to reapply: + +```typescript +// Look in the source schema file for patterns like: +// .superRefine(createSomeRefinement()) +// .check(someValidationFunction) +``` + +### Invalid Custom Field Names + +Follow STIX custom property naming conventions: + +```typescript +// ✅ Correct - use your namespace prefix +x_myorg_custom_field: z.string() + +// ❌ Incorrect - generic x_custom may conflict +x_custom_field: z.string() +``` + +### Type Safety Issues + +Ensure TypeScript integration works correctly: + +```typescript +// Create proper type exports +export type ExtendedCampaign = z.infer; +export const extendedCampaignSchema = /* your schema */; + +// Use type assertions carefully in implementation classes +class ExtendedImpl extends BaseImpl { + getCustomField(): string { + return (this as ExtendedCampaign).x_custom_field || ''; + } +} +``` + +## Testing Extended Schemas + +Create comprehensive tests for your extensions: + +```typescript +import { describe, it, expect } from 'vitest'; + +describe('Extended Campaign Schema', () => { + it('should validate campaigns with custom fields', () => { + const validCampaign = { + // ... standard fields + x_custom_severity: 'high', + x_custom_confidence_score: 90 + }; + + expect(() => extendedCampaignSchema.parse(validCampaign)).not.toThrow(); + }); + + it('should enforce custom validation rules', () => { + const invalidCampaign = { + // ... standard fields + x_custom_confidence_score: 95 // High confidence without severity + }; + + expect(() => extendedCampaignSchema.parse(invalidCampaign)).toThrow(); + }); +}); +``` + +--- + +## Next Steps + +- **Validation**: Learn how to [validate custom bundles](./validate-bundles) with your extended schemas +- **Error Handling**: Implement [robust error handling](./error-handling) for validation failures +- **Reference**: Explore the [complete schema API](../reference/schemas/) + +Your extended schemas now preserve all ATT&CK validation while supporting your custom use cases! diff --git a/docusaurus/docs/how-to-guides/index.md b/docusaurus/docs/how-to-guides/index.md new file mode 100644 index 00000000..e039b2d9 --- /dev/null +++ b/docusaurus/docs/how-to-guides/index.md @@ -0,0 +1,48 @@ +# How-to Guides + +**Problem-oriented solutions for specific ATT&CK Data Model tasks** + +These guides provide direct solutions to common problems you'll encounter when working with the ATT&CK Data Model. Each guide assumes you have basic familiarity with the library and focuses on achieving specific goals quickly and efficiently. + +## Available Guides + +### Data Validation & Quality + +- **[How to validate custom STIX bundles](./validate-bundles)** - Ensure your custom ATT&CK data meets specification requirements +- **[How to handle parsing errors gracefully](./error-handling)** - Implement robust error handling for production applications + +### Customization & Extension + +- **[How to extend schemas with custom fields](./extend-schemas)** - Add your own properties to ATT&CK objects while preserving validation + +## Quick Solutions + +**Looking for something specific?** Use these shortcuts: + +| Problem | Solution | +|---------|----------| +| My custom STIX data fails validation | [Validate Custom Bundles](./validate-bundles) | +| I need to add custom fields to techniques | [Extend Schemas](./extend-schemas) | +| My application crashes on invalid data | [Error Handling](./error-handling) | +| I want to use multiple data sources | [Reference: DataSource API](../reference/api/data-sources) | +| I need to optimize performance | [Explanation: Performance Trade-offs](../explanation/trade-offs) | + +## How-to Guide Principles + +These guides follow a problem-solving approach: + +- **Goal-oriented**: Each guide solves a specific problem +- **Assumes knowledge**: You should be familiar with basic ATT&CK Data Model concepts +- **Practical focus**: Direct steps to achieve your goal +- **Flexible solutions**: Adaptable to different scenarios + +## Getting More Help + +- **New to the library?** Start with our [Tutorials](../tutorials/) +- **Need complete API details?** Check the [Reference](../reference/) +- **Want to understand design decisions?** Read the [Explanations](../explanation/) +- **Have a specific question?** Search the documentation or check existing [GitHub Issues](https://github.com/mitre-attack/attack-data-model/issues) + +--- + +**Can't find what you're looking for?** Consider [opening a GitHub Issue](https://github.com/mitre-attack/attack-data-model/issues/new) to request a new how-to guide. diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.md b/docusaurus/docs/how-to-guides/manage-data-sources.md new file mode 100644 index 00000000..ec739629 --- /dev/null +++ b/docusaurus/docs/how-to-guides/manage-data-sources.md @@ -0,0 +1,403 @@ +# How to Manage Data Sources + +**Switch between different ATT&CK data sources efficiently** + +This guide shows you how to manage multiple ATT&CK data sources, switch between different versions, and work with local files, URLs, and the official repository. + +## Problem Scenarios + +Use this guide when you need to: + +- Switch between different ATT&CK versions for compatibility testing +- Load ATT&CK data from local files instead of the internet +- Fetch data from custom URLs or mirrors +- Manage multiple data sources in a production application +- Cache and reuse data sources efficiently + +## Switch Between ATT&CK Versions + +### Compare Multiple Versions + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +async function compareVersions() { + const versions = ['15.0', '15.1']; + const models: { [version: string]: any } = {}; + + // Load multiple versions + for (const version of versions) { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: version, + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + models[version] = loadDataModel(uuid); + } + + // Compare technique counts + versions.forEach(version => { + const count = models[version].techniques.length; + console.log(`ATT&CK ${version}: ${count} techniques`); + }); +} +``` + +### Use Latest Version + +```typescript +// Omit version to get the latest available +const latestDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + // No version specified = latest + parsingMode: 'relaxed' +}); + +const uuid = await registerDataSource(latestDataSource); +const latestModel = loadDataModel(uuid); +``` + +## Load from Local Files + +### Prepare Local STIX Bundle + +First, save a STIX bundle JSON file locally: + +```bash +# Download ATT&CK data +curl -o enterprise-attack.json https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/enterprise-attack/enterprise-attack.json +``` + +### Load from Local File + +```typescript +import { DataSource } from '@mitre-attack/attack-data-model'; +import path from 'path'; + +async function loadLocalData() { + const filePath = path.resolve('./enterprise-attack.json'); + + const localDataSource = new DataSource({ + source: 'file', + filePath: filePath, + parsingMode: 'relaxed' + }); + + try { + const uuid = await registerDataSource(localDataSource); + const model = loadDataModel(uuid); + + console.log(`Loaded ${model.techniques.length} techniques from local file`); + return model; + + } catch (error) { + console.error('Failed to load local file:', error); + throw error; + } +} +``` + +## Load from Custom URLs + +### Load from URL Source + +```typescript +async function loadFromUrl() { + const customUrl = 'https://your-custom-server.com/attack-data.json'; + + const urlDataSource = new DataSource({ + source: 'url', + url: customUrl, + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(urlDataSource); + const model = loadDataModel(uuid); + + return model; +} +``` + +### Load with Custom Headers + +```typescript +async function loadWithAuth() { + const dataSource = new DataSource({ + source: 'url', + url: 'https://private-server.com/attack-data.json', + requestOptions: { + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'X-Custom-Header': 'value' + } + }, + parsingMode: 'strict' + }); + + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); +} +``` + +## Manage Multiple Data Sources + +### Data Source Registry Pattern + +```typescript +class AttackDataManager { + private dataSources: Map = new Map(); + + async registerSource(name: string, config: any): Promise { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + this.dataSources.set(name, uuid); + return uuid; + } + + getModel(name: string) { + const uuid = this.dataSources.get(name); + if (!uuid) { + throw new Error(`Data source '${name}' not registered`); + } + return loadDataModel(uuid); + } + + async setupCommonSources() { + // Enterprise latest + await this.registerSource('enterprise-latest', { + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + + // Enterprise v15.0 + await this.registerSource('enterprise-v15', { + source: 'attack', + domain: 'enterprise-attack', + version: '15.0', + parsingMode: 'relaxed' + }); + + // Mobile latest + await this.registerSource('mobile-latest', { + source: 'attack', + domain: 'mobile-attack', + parsingMode: 'relaxed' + }); + } +} + +// Usage +const manager = new AttackDataManager(); +await manager.setupCommonSources(); + +const enterpriseModel = manager.getModel('enterprise-latest'); +const mobileModel = manager.getModel('mobile-latest'); +``` + +## Handle Data Source Errors + +### Graceful Fallback Pattern + +```typescript +async function loadWithFallback() { + const fallbackSources = [ + // Try latest first + { + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }, + // Fallback to specific version + { + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }, + // Final fallback to local file + { + source: 'file', + filePath: './backup-enterprise-attack.json', + parsingMode: 'relaxed' + } + ]; + + for (const config of fallbackSources) { + try { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + console.log(`Successfully loaded from source: ${config.source}`); + return model; + + } catch (error) { + console.warn(`Failed to load from ${config.source}:`, error.message); + continue; + } + } + + throw new Error('All data sources failed'); +} +``` + +### Validate Data Source Before Use + +```typescript +async function validateDataSource(config: any): Promise { + try { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + // Basic validation checks + const hasMinimumTechniques = model.techniques.length > 100; + const hasBasicTactics = model.tactics.length > 5; + const hasGroups = model.groups.length > 0; + + if (hasMinimumTechniques && hasBasicTactics && hasGroups) { + console.log('✅ Data source validation passed'); + return true; + } else { + console.warn('⚠️ Data source seems incomplete'); + return false; + } + + } catch (error) { + console.error('❌ Data source validation failed:', error); + return false; + } +} +``` + +## Cache and Performance + +### Implement Simple Caching + +```typescript +class CachedDataManager { + private cache: Map = new Map(); + + private getCacheKey(config: any): string { + return JSON.stringify(config); + } + + async loadData(config: any) { + const cacheKey = this.getCacheKey(config); + + // Check cache first + if (this.cache.has(cacheKey)) { + console.log('📋 Loading from cache'); + return this.cache.get(cacheKey); + } + + // Load fresh data + console.log('🌐 Loading fresh data'); + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + // Cache the result + this.cache.set(cacheKey, model); + + return model; + } + + clearCache() { + this.cache.clear(); + console.log('🗑️ Cache cleared'); + } +} +``` + +## Production Configuration + +### Environment-Based Data Sources + +```typescript +function getDataSourceConfig(): any { + const environment = process.env.NODE_ENV || 'development'; + + switch (environment) { + case 'production': + return { + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin version in production + parsingMode: 'strict' // Strict validation in production + }; + + case 'staging': + return { + source: 'url', + url: process.env.STAGING_ATTACK_URL, + parsingMode: 'relaxed' + }; + + case 'development': + default: + return { + source: 'file', + filePath: './dev-data/enterprise-attack.json', + parsingMode: 'relaxed' + }; + } +} + +// Usage +const config = getDataSourceConfig(); +const dataSource = new DataSource(config); +``` + +## Monitoring and Logging + +### Add Data Source Monitoring + +```typescript +async function loadWithMonitoring(config: any) { + const startTime = Date.now(); + + try { + console.log('📡 Starting data source load:', config.source); + + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + const loadTime = Date.now() - startTime; + console.log(`✅ Load completed in ${loadTime}ms`); + console.log(`📊 Loaded: ${model.techniques.length} techniques, ${model.groups.length} groups`); + + return model; + + } catch (error) { + const loadTime = Date.now() - startTime; + console.error(`❌ Load failed after ${loadTime}ms:`, error.message); + throw error; + } +} +``` + +## Key Takeaways + +- **Version Management**: Pin specific versions in production, use latest for development +- **Fallback Strategy**: Always have backup data sources for reliability +- **Validation**: Verify data quality after loading, especially with custom sources +- **Caching**: Implement caching for frequently accessed data to improve performance +- **Monitoring**: Log data source performance and success rates for troubleshooting +- **Environment Configuration**: Use different data sources for different deployment environments + +## Related Guides + +- [Handle Parsing Errors Gracefully](./error-handling) - Manage data quality issues +- [Extend Schemas with Custom Fields](./extend-schemas) - Customize data structures +- [Performance Optimization](./performance) - Scale for large datasets + +--- + +**You're now equipped to manage ATT&CK data sources effectively in any environment!** diff --git a/docusaurus/docs/how-to-guides/performance.md b/docusaurus/docs/how-to-guides/performance.md new file mode 100644 index 00000000..d44205bc --- /dev/null +++ b/docusaurus/docs/how-to-guides/performance.md @@ -0,0 +1,534 @@ +# How to Optimize Performance + +**Scale ATT&CK Data Model for large datasets and production workloads** + +This guide shows you how to optimize performance when working with large ATT&CK datasets, multiple domains, or production applications with high throughput requirements. + +## Problem Scenarios + +Use this guide when you experience: + +- Slow data loading times with large ATT&CK datasets +- High memory usage when working with multiple domains +- Performance bottlenecks in relationship navigation +- Need to optimize for production throughput +- Requirements for concurrent data access + +## Optimize Data Loading + +### Use Relaxed Parsing Mode + +```typescript +// Faster loading - continues on validation errors +const fastDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' // Skip strict validation for speed +}); + +// vs slower but more thorough validation +const strictDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' // Full validation - slower but safer +}); +``` + +### Parallelize Multiple Domain Loading + +```typescript +async function loadDomainsInParallel() { + const domains = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + // Load all domains simultaneously instead of sequentially + const loadPromises = domains.map(async (domain) => { + const dataSource = new DataSource({ + source: 'attack', + domain: domain.name, + version: '15.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + return { + domain: domain.name, + label: domain.label, + model: loadDataModel(uuid) + }; + }); + + // Wait for all to complete + const results = await Promise.all(loadPromises); + + return results.reduce((acc, result) => { + acc[result.domain] = result.model; + return acc; + }, {} as { [key: string]: any }); +} +``` + +## Memory Optimization + +### Selective Data Loading + +```typescript +class SelectiveAttackDataModel { + private fullModel: any; + + constructor(model: any) { + this.fullModel = model; + } + + // Only load techniques when needed + getTechniques(): any[] { + return this.fullModel.techniques; + } + + // Get lightweight technique summaries + getTechniqueSummaries(): Array<{id: string, name: string, attackId: string}> { + return this.fullModel.techniques.map((t: any) => ({ + id: t.id, + name: t.name, + attackId: t.external_references[0].external_id + })); + } + + // Get specific technique by ID without loading all + getTechniqueById(id: string): any { + return this.fullModel.techniques.find((t: any) => t.id === id); + } + + // Release memory for unused data + clearUnusedData() { + // Remove large description fields if not needed + this.fullModel.techniques.forEach((t: any) => { + if (t.description && t.description.length > 1000) { + t.description = t.description.substring(0, 200) + '...'; + } + }); + } +} +``` + +### Implement Data Streaming + +```typescript +class StreamingAttackProcessor { + private batchSize = 50; + + async processTechniquesInBatches( + model: any, + processor: (batch: any[]) => Promise + ) { + const techniques = model.techniques; + + for (let i = 0; i < techniques.length; i += this.batchSize) { + const batch = techniques.slice(i, i + this.batchSize); + + // Process batch + await processor(batch); + + // Allow event loop to breathe + await new Promise(resolve => setImmediate(resolve)); + + console.log(`Processed ${Math.min(i + this.batchSize, techniques.length)}/${techniques.length} techniques`); + } + } +} + +// Usage +const processor = new StreamingAttackProcessor(); +await processor.processTechniquesInBatches(attackModel, async (batch) => { + // Process each batch of techniques + batch.forEach(technique => { + // Your processing logic here + console.log(`Processing: ${technique.name}`); + }); +}); +``` + +## Optimize Relationship Navigation + +### Cache Relationship Results + +```typescript +class CachedRelationshipNavigator { + private relationshipCache = new Map(); + + getTactics(technique: any): any[] { + const cacheKey = `tactics_${technique.id}`; + + if (this.relationshipCache.has(cacheKey)) { + return this.relationshipCache.get(cacheKey)!; + } + + const tactics = technique.getTactics(); + this.relationshipCache.set(cacheKey, tactics); + + return tactics; + } + + getMitigations(technique: any): any[] { + const cacheKey = `mitigations_${technique.id}`; + + if (this.relationshipCache.has(cacheKey)) { + return this.relationshipCache.get(cacheKey)!; + } + + const mitigations = technique.getMitigations(); + this.relationshipCache.set(cacheKey, mitigations); + + return mitigations; + } + + clearCache() { + this.relationshipCache.clear(); + } + + getCacheStats() { + return { + size: this.relationshipCache.size, + memory: JSON.stringify([...this.relationshipCache.entries()]).length + }; + } +} +``` + +### Pre-compute Common Relationships + +```typescript +class RelationshipIndexer { + private techniqueToTactics = new Map(); + private tacticToTechniques = new Map(); + + buildIndexes(model: any) { + console.log('🔄 Building relationship indexes...'); + const startTime = Date.now(); + + // Index technique → tactics relationships + model.techniques.forEach((technique: any) => { + const tacticIds = technique.getTactics().map((t: any) => t.id); + this.techniqueToTactics.set(technique.id, tacticIds); + + // Build reverse index + tacticIds.forEach(tacticId => { + if (!this.tacticToTechniques.has(tacticId)) { + this.tacticToTechniques.set(tacticId, []); + } + this.tacticToTechniques.get(tacticId)!.push(technique.id); + }); + }); + + const buildTime = Date.now() - startTime; + console.log(`✅ Indexes built in ${buildTime}ms`); + console.log(`📊 Indexed ${this.techniqueToTactics.size} techniques`); + } + + // Fast lookup without method calls + getTacticIdsForTechnique(techniqueId: string): string[] { + return this.techniqueToTactics.get(techniqueId) || []; + } + + getTechniqueIdsForTactic(tacticId: string): string[] { + return this.tacticToTechniques.get(tacticId) || []; + } +} +``` + +## Concurrent Access Patterns + +### Thread-Safe Data Access + +```typescript +import { Worker, isMainThread, parentPort, workerData } from 'worker_threads'; + +class ConcurrentAttackAnalyzer { + async analyzeInWorkers(model: any, numWorkers = 4) { + if (!isMainThread) { + // Worker thread code + this.workerAnalysis(workerData); + return; + } + + // Main thread - distribute work + const techniques = model.techniques; + const chunkSize = Math.ceil(techniques.length / numWorkers); + const workers: Worker[] = []; + const results: any[] = []; + + for (let i = 0; i < numWorkers; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, techniques.length); + const chunk = techniques.slice(start, end); + + const worker = new Worker(__filename, { + workerData: { techniques: chunk, workerId: i } + }); + + workers.push(worker); + + worker.on('message', (result) => { + results.push(result); + + if (results.length === numWorkers) { + // All workers completed + workers.forEach(w => w.terminate()); + this.combineResults(results); + } + }); + } + } + + private workerAnalysis(data: any) { + const { techniques, workerId } = data; + + // Perform analysis on this chunk + const analysis = techniques.map((t: any) => ({ + id: t.id, + name: t.name, + tacticCount: t.getTactics().length, + mitigationCount: t.getMitigations().length + })); + + parentPort?.postMessage({ + workerId, + results: analysis + }); + } + + private combineResults(results: any[]) { + const combined = results.flatMap(r => r.results); + console.log(`📊 Analyzed ${combined.length} techniques across ${results.length} workers`); + } +} +``` + +## Production Optimization + +### Connection Pooling for Multiple Requests + +```typescript +class AttackDataPool { + private pool: any[] = []; + private maxSize = 10; + private currentSize = 0; + + async getModel(): Promise { + if (this.pool.length > 0) { + return this.pool.pop(); + } + + if (this.currentSize < this.maxSize) { + this.currentSize++; + return await this.createModel(); + } + + // Wait for a model to be returned to the pool + return new Promise((resolve) => { + const checkPool = () => { + if (this.pool.length > 0) { + resolve(this.pool.pop()); + } else { + setTimeout(checkPool, 10); + } + }; + checkPool(); + }); + } + + returnModel(model: any) { + this.pool.push(model); + } + + private async createModel(): Promise { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); + } +} + +// Usage +const pool = new AttackDataPool(); + +async function handleRequest(req: any, res: any) { + const model = await pool.getModel(); + + try { + // Process request using model + const techniques = model.techniques.slice(0, 10); + res.json(techniques); + } finally { + pool.returnModel(model); + } +} +``` + +### Lazy Loading with Proxies + +```typescript +class LazyAttackModel { + private model: any; + private loadedSections = new Set(); + + constructor(model: any) { + this.model = model; + + // Create proxy for lazy loading + return new Proxy(this, { + get(target, prop: string) { + if (prop === 'techniques' && !target.loadedSections.has('techniques')) { + console.log('🔄 Lazy loading techniques...'); + target.loadedSections.add('techniques'); + // Trigger any expensive initialization here + } + + return target.model[prop]; + } + }); + } +} +``` + +## Monitoring Performance + +### Add Performance Metrics + +```typescript +class PerformanceMonitor { + private metrics = new Map(); + + time(operation: string, fn: () => T): T { + const start = performance.now(); + const result = fn(); + const duration = performance.now() - start; + + this.recordMetric(operation, duration); + + return result; + } + + async timeAsync(operation: string, fn: () => Promise): Promise { + const start = performance.now(); + const result = await fn(); + const duration = performance.now() - start; + + this.recordMetric(operation, duration); + + return result; + } + + private recordMetric(operation: string, duration: number) { + if (!this.metrics.has(operation)) { + this.metrics.set(operation, []); + } + this.metrics.get(operation)!.push(duration); + } + + getStats(operation: string) { + const times = this.metrics.get(operation) || []; + + if (times.length === 0) return null; + + const sorted = [...times].sort((a, b) => a - b); + + return { + count: times.length, + average: times.reduce((a, b) => a + b) / times.length, + median: sorted[Math.floor(sorted.length / 2)], + min: sorted[0], + max: sorted[sorted.length - 1], + p95: sorted[Math.floor(sorted.length * 0.95)] + }; + } + + printAllStats() { + console.log('\n📊 Performance Stats:'); + for (const [operation, _] of this.metrics) { + const stats = this.getStats(operation); + if (stats) { + console.log(`${operation}:`); + console.log(` Average: ${stats.average.toFixed(2)}ms`); + console.log(` Median: ${stats.median.toFixed(2)}ms`); + console.log(` 95th percentile: ${stats.p95.toFixed(2)}ms`); + console.log(` Count: ${stats.count}`); + } + } + } +} + +// Usage +const monitor = new PerformanceMonitor(); + +const model = await monitor.timeAsync('data-loading', async () => { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); +}); + +const tactics = monitor.time('get-tactics', () => { + return model.techniques[0].getTactics(); +}); + +monitor.printAllStats(); +``` + +## Configuration for Scale + +### Production Environment Variables + +```bash +# .env.production +NODE_ENV=production +ATTACK_PARSING_MODE=strict +ATTACK_VERSION=15.1 +ATTACK_CACHE_SIZE=100 +ATTACK_WORKER_THREADS=4 +ATTACK_MEMORY_LIMIT=2048 +``` + +```typescript +// Production configuration +const config = { + parsingMode: process.env.ATTACK_PARSING_MODE || 'relaxed', + version: process.env.ATTACK_VERSION || '15.1', + cacheSize: parseInt(process.env.ATTACK_CACHE_SIZE || '10'), + workerThreads: parseInt(process.env.ATTACK_WORKER_THREADS || '2'), + memoryLimit: parseInt(process.env.ATTACK_MEMORY_LIMIT || '1024') +}; +``` + +## Key Performance Tips + +1. **Use Relaxed Parsing** in development, strict in production +2. **Load Domains in Parallel** when you need multiple domains +3. **Cache Relationship Results** for frequently accessed data +4. **Pre-compute Indexes** for complex relationship queries +5. **Implement Lazy Loading** for large datasets +6. **Monitor Performance** with metrics and logging +7. **Use Worker Threads** for CPU-intensive analysis +8. **Pool Data Models** in high-throughput applications + +## Related Guides + +- [Manage Data Sources](./manage-data-sources) - Efficient data source management +- [Handle Parsing Errors](./error-handling) - Deal with data quality issues +- [Reference: Configuration](../reference/configuration) - All configuration options + +--- + +**You're now equipped to scale the ATT&CK Data Model for production workloads!** diff --git a/docusaurus/docs/how-to-guides/validate-bundles.md b/docusaurus/docs/how-to-guides/validate-bundles.md new file mode 100644 index 00000000..c7346e70 --- /dev/null +++ b/docusaurus/docs/how-to-guides/validate-bundles.md @@ -0,0 +1,413 @@ +# How to Validate Custom STIX Bundles + +**Ensure your custom ATT&CK data meets specification requirements** + +When working with custom STIX 2.1 bundles containing ATT&CK data, you need to validate that they conform to both the STIX specification and ATT&CK schema requirements. This guide shows you how to implement comprehensive validation for your custom data. + +## Problem + +You have custom STIX 2.1 bundles containing ATT&CK objects and need to: + +- Verify they meet STIX 2.1 specification requirements +- Ensure ATT&CK-specific validation rules are satisfied +- Handle validation errors appropriately +- Integrate validation into your data processing pipeline + +## Solution Overview + +Use the ATT&CK Data Model's validation system with proper error handling to validate custom bundles before processing them. + +## Step 1: Basic Bundle Validation + +Create a validation function for STIX bundles: + +```typescript +import { + registerDataSource, + loadDataModel, + DataSource, + stixBundleSchema +} from '@mitre-attack/attack-data-model'; +import { z } from 'zod'; +import fs from 'fs'; + +async function validateCustomBundle(filePath: string): Promise { + try { + // Read the bundle file + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Basic STIX bundle validation + const validatedBundle = stixBundleSchema.parse(bundleData); + + console.log('✅ Bundle structure is valid'); + console.log(`📦 Bundle contains ${validatedBundle.objects.length} objects`); + + return true; + + } catch (error) { + if (error instanceof z.ZodError) { + console.error('❌ Bundle validation failed:'); + error.errors.forEach(err => { + console.error(` - ${err.path.join('.')}: ${err.message}`); + }); + } else { + console.error('❌ Error reading bundle:', error); + } + return false; + } +} +``` + +## Step 2: ATT&CK Object Validation + +Validate individual ATT&CK objects within the bundle: + +```typescript +import { + techniqueSchema, + tacticSchema, + groupSchema, + malwareSchema, + toolSchema, + campaignSchema +} from '@mitre-attack/attack-data-model'; + +interface ValidationResult { + isValid: boolean; + errors: string[]; + objectCounts: { [key: string]: number }; +} + +function validateAttackObjects(bundle: any): ValidationResult { + const result: ValidationResult = { + isValid: true, + errors: [], + objectCounts: {} + }; + + // Schema mapping for ATT&CK objects + const schemaMap: { [key: string]: z.ZodSchema } = { + 'attack-pattern': techniqueSchema, + 'x-mitre-tactic': tacticSchema, + 'intrusion-set': groupSchema, + 'malware': malwareSchema, + 'tool': toolSchema, + 'campaign': campaignSchema + }; + + bundle.objects.forEach((obj: any, index: number) => { + const objType = obj.type; + + // Count objects by type + result.objectCounts[objType] = (result.objectCounts[objType] || 0) + 1; + + // Validate ATT&CK objects + if (schemaMap[objType]) { + try { + schemaMap[objType].parse(obj); + } catch (error) { + result.isValid = false; + if (error instanceof z.ZodError) { + error.errors.forEach(err => { + result.errors.push( + `Object ${index} (${objType}): ${err.path.join('.')}: ${err.message}` + ); + }); + } + } + } + }); + + return result; +} +``` + +## Step 3: Comprehensive Bundle Validator + +Combine validation steps into a comprehensive validator: + +```typescript +async function comprehensiveValidation(filePath: string): Promise { + console.log(`🔍 Validating bundle: ${filePath}\n`); + + try { + // Step 1: Read and parse + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Step 2: Validate bundle structure + console.log('📋 Validating STIX bundle structure...'); + const validatedBundle = stixBundleSchema.parse(bundleData); + console.log('✅ Bundle structure is valid\n'); + + // Step 3: Validate ATT&CK objects + console.log('🎯 Validating ATT&CK objects...'); + const objectValidation = validateAttackObjects(validatedBundle); + + // Display object counts + console.log('📊 Object counts:'); + Object.entries(objectValidation.objectCounts).forEach(([type, count]) => { + console.log(` ${type}: ${count}`); + }); + console.log(''); + + // Display validation results + if (objectValidation.isValid) { + console.log('✅ All ATT&CK objects are valid'); + } else { + console.log('❌ ATT&CK object validation failed:'); + objectValidation.errors.forEach(error => { + console.log(` - ${error}`); + }); + } + + // Step 4: Test loading with Data Model + console.log('\n🔄 Testing bundle loading...'); + const dataSource = new DataSource({ + source: 'file', + file: filePath, + parsingMode: 'strict' // Use strict mode for validation + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + const attackDataModel = loadDataModel(uuid); + console.log('✅ Bundle loads successfully in ATT&CK Data Model'); + console.log(`📈 Loaded ${attackDataModel.techniques.length} techniques`); + console.log(`📈 Loaded ${attackDataModel.tactics.length} tactics`); + } + + } catch (error) { + console.error('❌ Validation failed:', error); + + if (error instanceof z.ZodError) { + console.error('\n📝 Detailed errors:'); + error.errors.forEach(err => { + console.error(` - ${err.path.join('.')}: ${err.message}`); + }); + } + } +} +``` + +## Step 4: Batch Validation + +Validate multiple bundles at once: + +```typescript +async function validateMultipleBundles(bundlePaths: string[]): Promise { + console.log(`🔄 Validating ${bundlePaths.length} bundles...\n`); + + const results = await Promise.allSettled( + bundlePaths.map(async (path) => { + try { + await comprehensiveValidation(path); + return { path, success: true }; + } catch (error) { + return { path, success: false, error }; + } + }) + ); + + // Summary + const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length; + const failed = results.length - successful; + + console.log('\n📊 Validation Summary:'); + console.log(`✅ Successful: ${successful}`); + console.log(`❌ Failed: ${failed}`); + + if (failed > 0) { + console.log('\n❌ Failed bundles:'); + results.forEach(result => { + if (result.status === 'fulfilled' && !result.value.success) { + console.log(` - ${result.value.path}`); + } + }); + } +} +``` + +## Step 5: Integration into Your Pipeline + +Create a reusable validation utility: + +```typescript +export interface BundleValidationOptions { + strictMode?: boolean; + validateRelationships?: boolean; + allowedObjectTypes?: string[]; +} + +export class BundleValidator { + private options: BundleValidationOptions; + + constructor(options: BundleValidationOptions = {}) { + this.options = { + strictMode: true, + validateRelationships: true, + ...options + }; + } + + async validate(filePath: string): Promise { + try { + // Implementation combining all validation steps + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Validate with appropriate settings + const bundleValidation = stixBundleSchema.parse(bundleData); + const objectValidation = validateAttackObjects(bundleValidation); + + if (this.options.validateRelationships) { + // Test loading to validate relationships + const dataSource = new DataSource({ + source: 'file', + file: filePath, + parsingMode: this.options.strictMode ? 'strict' : 'relaxed' + }); + + await registerDataSource(dataSource); + } + + return { + isValid: objectValidation.isValid, + errors: objectValidation.errors, + objectCounts: objectValidation.objectCounts + }; + + } catch (error) { + return { + isValid: false, + errors: [`Validation failed: ${error}`], + objectCounts: {} + }; + } + } +} + +// Usage example +const validator = new BundleValidator({ + strictMode: true, + validateRelationships: true +}); + +const result = await validator.validate('my-custom-bundle.json'); +if (!result.isValid) { + console.error('Validation failed:', result.errors); +} +``` + +## Common Validation Issues + +### Missing Required Fields + +```typescript +// ❌ Invalid technique - missing required fields +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "name": "My Custom Technique" + // Missing: spec_version, created, modified, description, etc. +} + +// ✅ Valid technique with all required fields +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "spec_version": "2.1", + "created": "2023-01-01T00:00:00.000Z", + "modified": "2023-01-01T00:00:00.000Z", + "name": "My Custom Technique", + "description": "Description of the technique", + "x_mitre_attack_spec_version": "3.3.0", + "x_mitre_version": "1.0" +} +``` + +### Invalid ATT&CK IDs + +```typescript +// Validate ATT&CK ID format +function validateAttackId(id: string, objectType: string): boolean { + const patterns: { [key: string]: RegExp } = { + 'attack-pattern': /^T\d{4}(\.\d{3})?$/, // T1234 or T1234.001 + 'x-mitre-tactic': /^TA\d{4}$/, // TA0001 + 'intrusion-set': /^G\d{4}$/, // G0001 + 'malware': /^S\d{4}$/, // S0001 + 'tool': /^S\d{4}$/ // S0001 + }; + + const pattern = patterns[objectType]; + return pattern ? pattern.test(id) : true; +} +``` + +## Performance Considerations + +For large bundles, implement streaming validation: + +```typescript +import { Transform } from 'stream'; +import { parse } from 'stream-json'; +import StreamValues from 'stream-json/streamers/StreamValues'; + +async function validateLargeBundle(filePath: string): Promise { + const pipeline = fs.createReadStream(filePath) + .pipe(parse()) + .pipe(StreamValues.withParser()) + .pipe(new Transform({ + objectMode: true, + transform(chunk, encoding, callback) { + try { + // Validate individual objects + const obj = chunk.value; + // Perform validation on obj + callback(null, chunk); + } catch (error) { + callback(error); + } + } + })); + + return new Promise((resolve, reject) => { + pipeline.on('end', resolve); + pipeline.on('error', reject); + }); +} +``` + +## Integration with CI/CD + +Add validation to your build pipeline: + +```bash +#!/bin/bash +# validate-bundles.sh + +echo "Validating ATT&CK bundles..." + +for bundle in data/*.json; do + echo "Validating $bundle..." + npx tsx validate-bundle.ts "$bundle" + + if [ $? -ne 0 ]; then + echo "❌ Validation failed for $bundle" + exit 1 + fi +done + +echo "✅ All bundles validated successfully" +``` + +--- + +## Next Steps + +- **Error Handling**: Learn [how to handle parsing errors gracefully](./error-handling) +- **Schema Extension**: See [how to extend schemas with custom fields](./extend-schemas) +- **Reference**: Check the [complete API documentation](../reference/) + +Your custom STIX bundles should now validate correctly and load reliably in the ATT&CK Data Model! diff --git a/docusaurus/docs/index.md b/docusaurus/docs/index.md new file mode 100644 index 00000000..702b5eda --- /dev/null +++ b/docusaurus/docs/index.md @@ -0,0 +1,75 @@ +# ATT&CK Data Model Documentation + +*A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1 bundles* + +Welcome to the documentation for the ATT&CK Data Model library. +This documentation aims to provide you with exactly the right type of information for your needs, whether you are a beginner or a seasoned pro. + +## Quick Start + +```bash +# Install the library +npm install @mitre-attack/attack-data-model + +# Import and use +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +``` + +## What is the ATT&CK Data Model? + +The ATT&CK Data Model (ADM) is a TypeScript library that strives to be: + +- **Type-Safe**: Full TypeScript support with compile-time validation +- **STIX 2.1 Compliant**: Standards-compliant +- **Relationship Navigation**: Intuitive methods for exploring connections +- **Multiple Data Sources**: Official repository, local files, URLs, TAXII + +## Zod Schema Foundation + +The ATT&CK Data Model leverages **[Zod](https://zod.dev/)**, a TypeScript-first schema validation library, to ensure data integrity and enable powerful developer experiences: + +### Key Benefits + +- **Data Validation**: Parse and validate ATT&CK data as strongly-typed TypeScript objects +- **Error Prevention**: Catch data inconsistencies at compile-time and runtime +- **Maintainability**: Single source of truth for data structures reduces maintenance overhead + +### Schema-Driven Development + +```typescript +import { techniqueSchema } from '@mitre-attack/attack-data-model'; + +// Automatic validation and type inference +const technique = techniqueSchema.parse(rawAttackData); +console.log(technique.name); // TypeScript knows this is a string +``` + +## Known Compliance Status + +**Current State**: The ATT&CK knowledge base does not fully conform to all defined schemas due to evolving data quality standards. + +**Our Approach**: + +- **Continuous Improvement**: Known discrepancies are actively tracked and addressed +- **Flexibility**: Library supports both `strict` and `relaxed` parsing modes +- **Transparency**: Validation errors are clearly documented and reported + +**For Users**: Use `relaxed` mode for production workflows while we work toward full schema compliance. + +## Current Version Information + +- **Library Version**: Latest release from npm +- **ATT&CK Specification**: 3.3.0 +- **STIX Version**: 2.1 +- **Node.js**: 20.0.0+ +- **TypeScript**: 4.5.0+ + +## Community and Support + +- **Browse these docs** for comprehensive guidance on the ATT&CK Data Model library +- **[Report issues](https://github.com/mitre-attack/attack-data-model/issues)** on GitHub +- **[Contact](https://attack.mitre.org/resources/engage-with-attack/contact/)** the MITRE ATT&CK team for questions about ATT&CK itself + +--- + +Ready to get started? Check out the links in the sidebar to dive right in! diff --git a/docusaurus/docs/overview.md b/docusaurus/docs/overview.md index c0b30b5f..1f4d108e 100644 --- a/docusaurus/docs/overview.md +++ b/docusaurus/docs/overview.md @@ -1,3 +1,237 @@ -# Overview +# ATT&CK Data Model Overview -// automate the overview summary here \ No newline at end of file +**A comprehensive TypeScript library for MITRE ATT&CK® data** + +This page provides a high-level overview of the ATT&CK Data Model library architecture, its core concepts, and how all the pieces fit together. + +## What is the ATT&CK Data Model? + +The ATT&CK Data Model (ADM) is a TypeScript library that provides type-safe, programmatic access to MITRE ATT&CK® datasets. It bridges the gap between raw STIX 2.1 data and developer-friendly TypeScript objects. + +### Core Value Proposition + +- **🔒 Type Safety**: Full TypeScript support prevents runtime errors +- **✅ STIX 2.1 Compliance**: Maintains standards compliance while adding usability +- **🔗 Relationship Navigation**: Intuitive methods for exploring ATT&CK connections +- **📊 Multi-Domain Support**: Works with Enterprise, Mobile, and ICS domains +- **🚀 Performance Optimized**: Designed for both memory efficiency and query speed + +## Architecture Overview + +``` +┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ +│ Data Sources │ │ Validation │ │ Object Model │ +│ │ │ │ │ │ +│ • GitHub Repository │───▶│ • Zod Schemas │───▶│ • ES6 Classes │ +│ • Local Files │ │ • STIX 2.1 Spec │ │ • Type Definitions │ +│ • Custom URLs │ │ • ATT&CK Rules │ │ • Relationship APIs │ +│ • TAXII Servers │ │ │ │ │ +└─────────────────────┘ └──────────────────┘ └─────────────────────┘ + │ + ┌──────────────────┐ + │ AttackDataModel │ + │ │ + │ • Central Hub │ + │ • Collections │ + │ • Relationships │ + └──────────────────┘ +``` + +## Core Components + +### 1. Data Sources (`src/data-sources/`) + +Handles loading ATT&CK data from various sources: + +- **attack**: Official MITRE ATT&CK GitHub repository +- **file**: Local JSON files containing STIX 2.1 bundles +- **url**: Remote URLs serving STIX 2.1 content +- **taxii**: TAXII 2.1 servers (planned) + +### 2. Validation Layer (`src/schemas/`) + +Ensures data integrity through Zod schemas: + +- **STIX 2.1 Base**: Foundation schemas following STIX specification +- **ATT&CK Extensions**: Custom fields and relationships specific to ATT&CK +- **Refinements**: Advanced validation rules for ATT&CK-specific constraints + +### 3. Object Model (`src/classes/`) + +Provides developer-friendly interfaces: + +- **Implementation Classes**: ES6 classes for each ATT&CK object type +- **Relationship Methods**: Navigate connections between objects intuitively +- **Type Safety**: Full TypeScript support with compile-time checking + +### 4. AttackDataModel (`src/classes/attack-data-model.ts`) + +Central hub containing all ATT&CK objects with automatic relationship mapping. + +## Object Type Hierarchy + +### STIX Domain Objects (SDOs) + +Core ATT&CK concepts represented as STIX objects: + +| ATT&CK Concept | STIX Type | Custom? | Description | +|----------------|-----------|---------|-------------| +| **Technique** | `attack-pattern` | No | Methods adversaries use to achieve goals | +| **Tactic** | `x-mitre-tactic` | Yes | Adversary tactical objectives | +| **Group** | `intrusion-set` | No | Adversary organizations | +| **Software** | `malware`/`tool` | No | Adversary tools and malware | +| **Mitigation** | `course-of-action` | No | Defensive countermeasures | +| **Campaign** | `campaign` | No | Sets of adversary activities | +| **Data Source** | `x-mitre-data-source` | Yes | Detection data categories | +| **Matrix** | `x-mitre-matrix` | Yes | Organizational structure | + +### STIX Relationship Objects (SROs) + +Connections between ATT&CK objects: + +- **uses**: Groups/campaigns/software using techniques +- **mitigates**: Mitigations addressing techniques +- **subtechnique-of**: Sub-technique to parent relationships +- **detects**: Data components detecting techniques + +## Data Flow + +### Registration Process + +```typescript +// 1. Create data source configuration +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +// 2. Register and validate data +const uuid = await registerDataSource(dataSource); + +// 3. Load typed data model +const attackDataModel = loadDataModel(uuid); +``` + +### Validation Pipeline + +1. **Raw STIX Data**: JSON from data source +2. **Schema Validation**: Zod schemas ensure STIX compliance +3. **Refinement Checks**: ATT&CK-specific validation rules +4. **Object Creation**: Conversion to TypeScript classes +5. **Relationship Mapping**: Automatic linking between objects + +### Relationship Navigation + +```typescript +const technique = attackDataModel.techniques[0]; + +// Navigate relationships using intuitive methods +const tactics = technique.getTactics(); // Associated tactics +const groups = technique.getGroups(); // Groups using this technique +const mitigations = technique.getMitigations(); // Available mitigations +const parent = technique.getParentTechnique(); // Parent (if sub-technique) +``` + +## Multi-Domain Support + +The library supports all three ATT&CK domains: + +### Enterprise Domain (`enterprise-attack`) + +- Traditional IT environments +- Most comprehensive technique coverage +- Extensive group and software attribution + +### Mobile Domain (`mobile-attack`) + +- Mobile device threats +- Platform-specific techniques +- App store and mobile-specific tactics + +### ICS Domain (`ics-attack`) + +- Industrial Control Systems +- Operational Technology focus +- Critical infrastructure contexts + +## Extensibility + +### Custom Fields + +Extend ATT&CK objects with custom properties while maintaining compliance: + +```typescript +const customTechniqueSchema = techniqueSchema.extend({ + custom_severity: z.number().optional(), + custom_tags: z.array(z.string()).optional() +}); +``` + +### Custom Refinements + +Apply additional validation rules: + +```typescript +const refinedSchema = customTechniqueSchema.check((data) => { + // Custom validation logic + return data.custom_severity <= 10; +}); +``` + +## Performance Characteristics + +### Memory Usage + +- **Efficient Object Storage**: Optimized class instances +- **Lazy Relationship Loading**: Relationships computed on demand +- **Configurable Caching**: Balance memory vs. performance + +### Query Performance + +- **Direct Property Access**: No query parsing overhead +- **Pre-computed Relationships**: Fast navigation between objects +- **TypeScript Optimization**: Compile-time optimizations + +## Standards Compliance + +### STIX 2.1 Foundation + +- Full compliance with STIX 2.1 specification +- Support for all STIX Domain and Relationship Objects +- Extensible through STIX custom properties pattern + +### ATT&CK Specification + +- Implements ATT&CK Specification 3.3.0 +- Support for all ATT&CK object types and relationships +- Backwards compatibility with previous versions + +## Integration Patterns + +### Application Integration + +- Import as npm package +- TypeScript-first development experience +- Works with any JavaScript framework + +### Data Pipeline Integration + +- Stream processing support +- Batch analysis capabilities +- Export to various formats + +### Security Tool Integration + +- SIEM integration patterns +- Threat hunting query generation +- Detection rule development + +--- + +**Ready to dive deeper?** Explore our comprehensive documentation: + +- **[Tutorials](./tutorials/)** - Learn by building +- **[How-to Guides](./how-to-guides/)** - Solve specific problems +- **[Reference](./reference/)** - Complete API documentation +- **[Explanation](./explanation/)** - Understand the design diff --git a/docusaurus/docs/reference/api/attack-data-model.md b/docusaurus/docs/reference/api/attack-data-model.md new file mode 100644 index 00000000..80f13043 --- /dev/null +++ b/docusaurus/docs/reference/api/attack-data-model.md @@ -0,0 +1,306 @@ +# AttackDataModel + +**Main class containing all ATT&CK objects with automatic relationship mapping** + +The `AttackDataModel` class is the central data structure that holds all ATT&CK objects and provides access to them through typed properties. It automatically processes relationships between objects and provides convenient access methods. + +## Constructor + +```typescript +new AttackDataModel(uuid: string, attackObjects: AttackObject[]) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `uuid` | `string` | Unique identifier for this data model instance | +| `attackObjects` | `AttackObject[]` | Array of validated ATT&CK objects to populate the model | + +### Example + +```typescript +import { AttackDataModel } from '@mitre-attack/attack-data-model'; + +// Usually created internally by loadDataModel() +const attackDataModel = new AttackDataModel(uuid, validatedObjects); +``` + +## Properties + +### Object Collections + +All ATT&CK objects are organized into typed arrays accessible as properties: + +| Property | Type | Description | +|----------|------|-------------| +| `techniques` | `TechniqueImpl[]` | All techniques and sub-techniques | +| `tactics` | `TacticImpl[]` | All tactics (adversary goals) | +| `groups` | `GroupImpl[]` | All threat actor groups | +| `campaigns` | `CampaignImpl[]` | All attack campaigns | +| `malware` | `MalwareImpl[]` | All malware software | +| `tools` | `ToolImpl[]` | All legitimate tools used maliciously | +| `mitigations` | `MitigationImpl[]` | All defensive measures | +| `relationships` | `RelationshipImpl[]` | All relationships between objects | +| `matrices` | `MatrixImpl[]` | All ATT&CK matrices | +| `dataSources` | `DataSourceImpl[]` | All data sources *(deprecated)* | +| `dataComponents` | `DataComponentImpl[]` | All data components *(deprecated)* | +| `assets` | `AssetImpl[]` | All targeted assets | +| `detectionStrategies` | `DetectionStrategyImpl[]` | All detection strategies | +| `analytics` | `AnalyticImpl[]` | All analytics | +| `logSources` | `LogSourceImpl[]` | All log sources | + +### Example Access + +```typescript +const attackDataModel = loadDataModel(uuid); + +// Access techniques +console.log(`Loaded ${attackDataModel.techniques.length} techniques`); + +// Access tactics +const initialAccess = attackDataModel.tactics.find(t => + t.x_mitre_shortname === 'initial-access' +); + +// Access groups +const apt1 = attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' +); +``` + +## Methods + +### getUuid() + +Returns the unique identifier for this data model instance. + +```typescript +getUuid(): string +``` + +**Returns**: The UUID assigned during data source registration. + +**Example**: + +```typescript +const modelId = attackDataModel.getUuid(); +console.log(`Model ID: ${modelId}`); +``` + +### getObjectById() + +Retrieves any ATT&CK object by its STIX ID. + +```typescript +getObjectById(id: string): AttackObject | undefined +``` + +**Parameters**: + +- `id` - STIX identifier (e.g., `"attack-pattern--12345678-..."`) + +**Returns**: The object with matching STIX ID, or `undefined` if not found. + +**Example**: + +```typescript +const technique = attackDataModel.getObjectById( + 'attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298' +); + +if (technique) { + console.log(`Found: ${technique.name}`); +} +``` + +### getObjectByAttackId() + +Retrieves any ATT&CK object by its ATT&CK ID. + +```typescript +getObjectByAttackId(attackId: string): AttackObject | undefined +``` + +**Parameters**: + +- `attackId` - ATT&CK identifier (e.g., `"T1055"`, `"G0006"`, `"S0001"`) + +**Returns**: The object with matching ATT&CK ID, or `undefined` if not found. + +**Example**: + +```typescript +const technique = attackDataModel.getObjectByAttackId('T1055'); +const group = attackDataModel.getObjectByAttackId('G0006'); +const software = attackDataModel.getObjectByAttackId('S0001'); + +if (technique) { + console.log(`T1055: ${technique.name}`); +} +``` + +### getAllObjects() + +Returns all objects in the data model as a flat array. + +```typescript +getAllObjects(): AttackObject[] +``` + +**Returns**: Array containing all objects from all collections. + +**Example**: + +```typescript +const allObjects = attackDataModel.getAllObjects(); +console.log(`Total objects: ${allObjects.length}`); + +// Count by type +const counts = allObjects.reduce((acc, obj) => { + acc[obj.type] = (acc[obj.type] || 0) + 1; + return acc; +}, {} as Record); + +console.log('Object counts:', counts); +``` + +## Relationship Processing + +The `AttackDataModel` automatically processes all relationship objects during construction to enable convenient navigation between related objects. + +### Automatic Relationship Mapping + +When the data model is created, it: + +1. **Indexes all relationships** by source and target IDs +2. **Creates bidirectional mappings** where appropriate +3. **Populates navigation methods** on implementation classes +4. **Validates relationship integrity** in strict mode + +### Relationship Types Processed + +| Relationship | Description | Navigation Methods | +|--------------|-------------|-------------------| +| `uses` | Groups/Software → Techniques | `getTechniques()`, `getSoftware()` | +| `mitigates` | Mitigations → Techniques | `getMitigations()`, `getTechniques()` | +| `subtechnique-of` | Sub-techniques → Parent | `getSubtechniques()`, `getParentTechnique()` | +| `attributed-to` | Campaigns → Groups | `getAttributedTo()`, `getAssociatedCampaigns()` | +| `targets` | Techniques → Assets | `getTargets()`, `getTargetingTechniques()` | + +### Example: Relationship Navigation + +```typescript +// Start with a technique +const technique = attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' +); + +if (technique) { + // Navigate to related objects + const tactics = technique.getTactics(); + const mitigations = technique.getMitigations(); + const subtechniques = technique.getSubtechniques(); + + console.log(`${technique.name} is used for ${tactics.length} tactics`); + console.log(`${mitigations.length} mitigations available`); + console.log(`${subtechniques.length} sub-techniques defined`); +} +``` + +## Data Integrity + +### Validation + +The `AttackDataModel` ensures data integrity through: + +- **Schema validation** of all input objects +- **Relationship validation** between objects +- **ID uniqueness** checking +- **Reference integrity** validation + +### Error Handling + +In strict parsing mode, the following issues cause errors: + +- Missing required properties +- Invalid STIX ID formats +- Broken relationship references +- Duplicate object IDs + +In relaxed mode, these issues generate warnings but don't prevent data loading. + +## Performance Considerations + +### Memory Usage + +The `AttackDataModel` maintains all objects in memory for fast access. Typical memory usage: + +- **Enterprise ATT&CK**: ~50-100 MB +- **Mobile ATT&CK**: ~20-30 MB +- **ICS ATT&CK**: ~10-20 MB + +### Access Patterns + +For optimal performance: + +```typescript +// ✅ Efficient - direct property access +const techniques = attackDataModel.techniques; + +// ✅ Efficient - use built-in navigation +const tactics = technique.getTactics(); + +// ❌ Less efficient - repeated searches +const technique = attackDataModel.getAllObjects().find(/* ... */); +``` + +## Thread Safety + +The `AttackDataModel` is **read-only** after construction and safe for concurrent access across multiple threads or async operations. + +## Integration Patterns + +### Caching Data Models + +```typescript +const modelCache = new Map(); + +function getCachedModel(uuid: string): AttackDataModel { + if (!modelCache.has(uuid)) { + const model = loadDataModel(uuid); + modelCache.set(uuid, model); + } + return modelCache.get(uuid)!; +} +``` + +### Filtering and Searching + +```typescript +// Find techniques by platform +const windowsTechniques = attackDataModel.techniques.filter(t => + t.x_mitre_platforms?.includes('Windows') +); + +// Search by name +const credentialTechniques = attackDataModel.techniques.filter(t => + t.name.toLowerCase().includes('credential') +); + +// Complex filtering +const highValueTargets = attackDataModel.techniques.filter(t => { + const tactics = t.getTactics(); + return tactics.some(tactic => + tactic.x_mitre_shortname === 'credential-access' + ) && t.x_mitre_platforms?.includes('Windows'); +}); +``` + +--- + +## See Also + +- **[DataSource](./data-sources)** - Data source configuration and loading +- **[Implementation Classes](../schemas/sdo/)** - Individual object class documentation +- **[Relationships](../schemas/sro/relationship.schema)** - Relationship type specifications diff --git a/docusaurus/docs/reference/api/data-sources.md b/docusaurus/docs/reference/api/data-sources.md new file mode 100644 index 00000000..b0a6eb35 --- /dev/null +++ b/docusaurus/docs/reference/api/data-sources.md @@ -0,0 +1,427 @@ +# DataSource + +**Data source configuration and registration for loading ATT&CK datasets** + +The `DataSource` class defines where and how to load ATT&CK data. It supports multiple source types including the official ATT&CK repository, local files, remote URLs, and TAXII servers. + +## Constructor + +```typescript +new DataSource(options: DataSourceOptions) +``` + +### DataSourceOptions Interface + +```typescript +interface DataSourceOptions { + source: 'attack' | 'file' | 'url' | 'taxii'; + parsingMode?: 'strict' | 'relaxed'; + + // Attack source options + domain?: 'enterprise-attack' | 'mobile-attack' | 'ics-attack'; + version?: string; + + // File source options + file?: string; + + // URL source options + url?: string; + timeout?: number; + headers?: Record; + + // TAXII source options (coming soon) + server?: string; + collection?: string; + credentials?: { + username: string; + password: string; + }; +} +``` + +## Source Types + +### Attack Repository Source + +Load data from the official MITRE ATT&CK STIX 2.1 repository. + +```typescript +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Default | Description | +|--------|------|----------|---------|-------------| +| `source` | `'attack'` | ✅ | - | Specifies ATT&CK repository source | +| `domain` | `'enterprise-attack'` \| `'mobile-attack'` \| `'ics-attack'` | ✅ | - | ATT&CK domain to load | +| `version` | `string` | ❌ | `'latest'` | Specific version (e.g., '15.1') or 'latest' | +| `parsingMode` | `'strict'` \| `'relaxed'` | ❌ | `'strict'` | Validation strictness | + +**Available Versions**: + +- `'latest'` - Most recent release +- `'15.1'` - Version 15.1 (latest stable) +- `'15.0'` - Version 15.0 +- See [ATT&CK releases](https://github.com/mitre-attack/attack-stix-data/releases) for all versions + +**Domain Content**: + +- `enterprise-attack` - Techniques for Windows, Linux, macOS, containers, cloud +- `mobile-attack` - Mobile-specific techniques for Android and iOS +- `ics-attack` - Industrial Control Systems techniques + +### File Source + +Load data from local STIX 2.1 bundle files. + +```typescript +const dataSource = new DataSource({ + source: 'file', + file: '/path/to/enterprise-attack.json', + parsingMode: 'relaxed' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `source` | `'file'` | ✅ | Specifies file source | +| `file` | `string` | ✅ | Absolute or relative path to STIX bundle JSON file | +| `parsingMode` | `'strict'` \| `'relaxed'` | ❌ | Validation strictness (default: 'strict') | + +**File Requirements**: + +- Must be valid JSON format +- Must contain a STIX 2.1 bundle with `type: "bundle"` +- Should include ATT&CK objects with proper schemas + +**Example File Structure**: + +```json +{ + "type": "bundle", + "id": "bundle--12345678-1234-1234-1234-123456789012", + "objects": [ + { + "type": "attack-pattern", + "id": "attack-pattern--...", + "name": "Process Injection", + // ... other technique properties + } + // ... more objects + ] +} +``` + +### URL Source + +Load data from remote URLs serving STIX 2.1 content. + +```typescript +const dataSource = new DataSource({ + source: 'url', + url: 'https://example.com/attack-data.json', + timeout: 30000, + headers: { + 'Authorization': 'Bearer token123', + 'Accept': 'application/json' + }, + parsingMode: 'strict' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `source` | `'url'` | ✅ | Specifies URL source | +| `url` | `string` | ✅ | HTTP/HTTPS URL to STIX bundle | +| `timeout` | `number` | ❌ | Request timeout in milliseconds (default: 10000) | +| `headers` | `Record` | ❌ | HTTP headers for authentication/content negotiation | +| `parsingMode` | `'strict'` \| `'relaxed'` | ❌ | Validation strictness (default: 'strict') | + +**URL Requirements**: + +- Must be accessible via HTTP/HTTPS +- Should return JSON content with proper MIME type +- Must contain valid STIX 2.1 bundle + +### TAXII Source (Coming Soon) + +Load data from TAXII 2.1 servers. + +```typescript +const dataSource = new DataSource({ + source: 'taxii', + server: 'https://cti-taxii.mitre.org', + collection: 'attack-patterns', + credentials: { + username: 'user', + password: 'pass' + } +}); +``` + +> **Note**: TAXII source support is planned for future releases. + +## Parsing Modes + +### Strict Mode + +```typescript +parsingMode: 'strict' +``` + +**Behavior**: + +- All objects must pass schema validation +- Any validation failure aborts the entire loading process +- Relationships must reference valid objects +- Recommended for production use with trusted data sources + +**Use Cases**: + +- Production applications requiring data integrity +- Applications with strict compliance requirements +- Testing and validation scenarios + +### Relaxed Mode + +```typescript +parsingMode: 'relaxed' +``` + +**Behavior**: + +- Invalid objects are logged but skipped +- Loading continues even with validation errors +- Broken relationships are ignored +- Useful for experimental or incomplete datasets + +**Use Cases**: + +- Development and testing with custom data +- Loading datasets with known minor issues +- Research scenarios with experimental data + +## Registration and Loading + +### registerDataSource() + +Validates and registers a data source for use. + +```typescript +async function registerDataSource(dataSource: DataSource): Promise +``` + +**Parameters**: + +- `dataSource` - Configured DataSource instance + +**Returns**: + +- `string` - UUID for the registered data source on success +- `null` - Registration failed + +**Example**: + +```typescript +import { registerDataSource } from '@mitre-attack/attack-data-model'; + +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +try { + const uuid = await registerDataSource(dataSource); + if (uuid) { + console.log(`Data source registered: ${uuid}`); + } else { + console.error('Registration failed'); + } +} catch (error) { + console.error('Registration error:', error); +} +``` + +### loadDataModel() + +Loads a previously registered data source. + +```typescript +function loadDataModel(uuid: string): AttackDataModel +``` + +**Parameters**: + +- `uuid` - UUID returned from `registerDataSource()` + +**Returns**: + +- `AttackDataModel` - Populated data model instance + +**Throws**: + +- Error if UUID is invalid or data source not registered + +**Example**: + +```typescript +import { loadDataModel } from '@mitre-attack/attack-data-model'; + +const attackDataModel = loadDataModel(uuid); +console.log(`Loaded ${attackDataModel.techniques.length} techniques`); +``` + +## Data Source Validation + +During registration, data sources undergo validation: + +### Network Sources (attack, url) + +1. **Connectivity Check** - Verify the source is accessible +2. **Content Validation** - Ensure valid JSON and STIX structure +3. **Schema Compliance** - Validate ATT&CK objects against schemas +4. **Relationship Integrity** - Check all relationships reference valid objects + +### File Sources + +1. **File Existence** - Verify file exists and is readable +2. **JSON Parsing** - Ensure valid JSON format +3. **Bundle Structure** - Validate STIX bundle format +4. **Content Validation** - Same as network sources + +## Caching and Performance + +### Automatic Caching + +Registered data sources are cached in memory for fast repeated access: + +```typescript +// First registration - downloads and validates +const uuid1 = await registerDataSource(dataSource); + +// Subsequent loads - uses cached data +const model1 = loadDataModel(uuid1); +const model2 = loadDataModel(uuid1); // Fast - from cache +``` + +### Cache Management + +```typescript +// Check if a data source is cached +import { isDataSourceCached } from '@mitre-attack/attack-data-model'; + +if (isDataSourceCached(uuid)) { + console.log('Data source is cached'); +} + +// Clear cache (if needed) +import { clearDataSourceCache } from '@mitre-attack/attack-data-model'; +clearDataSourceCache(uuid); +``` + +## Error Handling + +### Common Errors + +| Error Type | Cause | Solution | +|------------|-------|----------| +| `NetworkError` | URL unreachable, timeout | Check network connectivity, increase timeout | +| `FileNotFoundError` | File path invalid | Verify file exists and is readable | +| `ValidationError` | Invalid STIX data | Fix data format or use relaxed mode | +| `AuthenticationError` | Invalid credentials | Check username/password for TAXII sources | + +### Error Examples + +```typescript +try { + const uuid = await registerDataSource(dataSource); +} catch (error) { + if (error.message.includes('ENOTFOUND')) { + console.error('Network error: Check internet connection'); + } else if (error.message.includes('timeout')) { + console.error('Request timeout: Try increasing timeout value'); + } else if (error.message.includes('validation')) { + console.error('Data validation failed: Check data format'); + } else { + console.error('Unknown error:', error); + } +} +``` + +## Configuration Examples + +### Complete Examples + +**Production Enterprise Setup**: + +```typescript +const productionSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +**Development with Local Data**: + +```typescript +const devSource = new DataSource({ + source: 'file', + file: './data/custom-attack.json', + parsingMode: 'relaxed' +}); +``` + +**Remote Data with Authentication**: + +```typescript +const remoteSource = new DataSource({ + source: 'url', + url: 'https://api.example.com/attack/data', + timeout: 60000, + headers: { + 'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOi...', + 'Accept': 'application/stix+json' + }, + parsingMode: 'strict' +}); +``` + +**Multi-Domain Loading**: + +```typescript +const domains = ['enterprise-attack', 'mobile-attack', 'ics-attack']; +const dataSources = domains.map(domain => new DataSource({ + source: 'attack', + domain: domain as any, + version: 'latest' +})); + +const registrations = await Promise.all( + dataSources.map(ds => registerDataSource(ds)) +); + +const models = registrations + .filter(uuid => uuid !== null) + .map(uuid => loadDataModel(uuid!)); +``` + +--- + +## See Also + +- **[AttackDataModel](./attack-data-model)** - Working with loaded data models +- **[Configuration Reference](../configuration)** - Complete configuration options +- **[Error Reference](../errors)** - Error handling and troubleshooting diff --git a/docusaurus/docs/reference/api/index.md b/docusaurus/docs/reference/api/index.md new file mode 100644 index 00000000..1b1b615a --- /dev/null +++ b/docusaurus/docs/reference/api/index.md @@ -0,0 +1,172 @@ +# API Reference + +**Complete documentation for all ATT&CK Data Model classes and functions** + +The ATT&CK Data Model provides a comprehensive TypeScript API for working with MITRE ATT&CK® data. This reference section documents all public classes, methods, and functions available in the library. + +## Core API Components + +### [AttackDataModel Class](./attack-data-model) + +The main data model class that provides access to all ATT&CK objects and their relationships. + +**Key Features:** + +- Access to techniques, tactics, groups, campaigns, and other ATT&CK objects +- Automatic relationship mapping between objects +- Type-safe collections with full TypeScript support +- Memory-efficient caching and indexing + +### [Data Source Management](./data-sources) + +Functions and classes for registering and managing ATT&CK data sources. + +**Key Features:** + +- Support for official ATT&CK repository, local files, and URLs +- Configurable parsing modes (strict/relaxed) +- Data source validation and caching +- Version management and compatibility checking + +### [Utility Functions](./utilities) + +Helper functions for common operations and data processing tasks. + +**Key Features:** + +- Data validation and transformation utilities +- Relationship traversal helpers +- Error handling and logging utilities +- Performance optimization helpers + +## Object Type Categories + +### STIX Domain Objects (SDO) + +**Techniques and Tactics:** + +- `TechniqueImpl` - Individual ATT&CK techniques +- `TacticImpl` - ATT&CK tactics and kill chain phases + +**Threat Actors and Campaigns:** + +- `GroupImpl` - Threat actor groups and APTs +- `CampaignImpl` - Threat campaigns and operations + +**Software and Tools:** + +- `MalwareImpl` - Malicious software used by threat actors +- `ToolImpl` - Tools and utilities used in attacks + +**Detection and Mitigation:** + +- `MitigationImpl` - Defensive measures and countermeasures +- `DataSourceImpl` - Detection data sources +- `DataComponentImpl` - Specific detection methods + +**Infrastructure and Assets:** + +- `AssetImpl` - Systems and infrastructure components +- `IdentityImpl` - Organizations and identity information +- `MatrixImpl` - ATT&CK framework matrices + +### STIX Relationship Objects (SRO) + +**Relationships:** + +- `RelationshipImpl` - Links between ATT&CK objects + +### STIX Meta Objects (SMO) + +**Metadata:** + +- `MarkingDefinitionImpl` - Data marking and classification + +## Common Usage Patterns + +### Loading Data Models + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +// Register a data source +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +const uuid = await registerDataSource(dataSource); +const attackDataModel = loadDataModel(uuid); +``` + +### Accessing ATT&CK Objects + +```typescript +// Get all techniques +const techniques = attackDataModel.techniques; + +// Get all tactics +const tactics = attackDataModel.tactics; + +// Get all groups +const groups = attackDataModel.groups; +``` + +### Relationship Navigation + +```typescript +// Get techniques used by a group +const group = attackDataModel.groups[0]; +const techniquesUsed = group.getTechniques(); + +// Get tactics for a technique +const technique = attackDataModel.techniques[0]; +const tactics = technique.getTactics(); + +// Get mitigations for a technique +const mitigations = technique.getMitigations(); +``` + +### Schema Validation + +```typescript +import { techniqueSchema } from '@mitre-attack/attack-data-model'; + +try { + const validTechnique = techniqueSchema.parse(rawTechniqueData); + console.log('Validation successful:', validTechnique.name); +} catch (error) { + console.error('Validation failed:', error); +} +``` + +## Error Handling + +All API functions and methods use consistent error handling patterns: + +**Validation Errors:** Zod validation errors with detailed field-level information +**Data Source Errors:** Network, file system, or data format errors +**Relationship Errors:** Missing or invalid relationship references + +See the [Error Reference](../errors) for complete error code documentation. + +## Performance Considerations + +**Memory Usage:** The library loads entire datasets into memory for optimal query performance +**Initialization Time:** Initial data loading and relationship mapping takes time proportional to dataset size +**Query Performance:** Object access and relationship traversal are optimized for speed after initialization + +## TypeScript Integration + +The library is designed for optimal TypeScript experience: + +**Full Type Safety:** All objects, properties, and methods are fully typed +**IDE Support:** Auto-completion, inline documentation, and error detection +**Generic Support:** Type parameters for custom extensions and filtering + +## Next Steps + +- Browse the [Schema Reference](../schemas/) for detailed field documentation +- Check the [Configuration Reference](../configuration) for setup options +- See [How-to Guides](../../how-to-guides/) for practical implementation examples diff --git a/docusaurus/docs/reference/api/utilities.md b/docusaurus/docs/reference/api/utilities.md new file mode 100644 index 00000000..5bf59b84 --- /dev/null +++ b/docusaurus/docs/reference/api/utilities.md @@ -0,0 +1,600 @@ +# Utility Functions + +**Helper functions and data manipulation tools** + +The ATT&CK Data Model provides various utility functions for common data manipulation, validation, and analysis tasks. These functions complement the main classes and provide convenient ways to work with ATT&CK data. + +## Data Source Utilities + +### isDataSourceCached() + +Check if a data source is currently cached in memory. + +```typescript +function isDataSourceCached(uuid: string): boolean +``` + +**Parameters**: + +- `uuid` - Data source UUID from registration + +**Returns**: `true` if cached, `false` otherwise + +**Example**: + +```typescript +import { isDataSourceCached } from '@mitre-attack/attack-data-model'; + +if (isDataSourceCached(uuid)) { + console.log('Data source is cached - loading will be fast'); +} else { + console.log('Data source will be loaded from storage'); +} +``` + +### clearDataSourceCache() + +Remove a specific data source from the cache. + +```typescript +function clearDataSourceCache(uuid: string): boolean +``` + +**Parameters**: + +- `uuid` - Data source UUID to remove from cache + +**Returns**: `true` if cache was cleared, `false` if UUID not found + +**Example**: + +```typescript +import { clearDataSourceCache } from '@mitre-attack/attack-data-model'; + +const cleared = clearDataSourceCache(uuid); +if (cleared) { + console.log('Cache cleared - next load will refresh data'); +} +``` + +### clearAllDataSourceCaches() + +Remove all data sources from the cache. + +```typescript +function clearAllDataSourceCaches(): number +``` + +**Returns**: Number of data sources that were cached + +**Example**: + +```typescript +import { clearAllDataSourceCaches } from '@mitre-attack/attack-data-model'; + +const clearedCount = clearAllDataSourceCaches(); +console.log(`Cleared ${clearedCount} cached data sources`); +``` + +## Object Identification Utilities + +### isValidStixId() + +Validate STIX ID format compliance. + +```typescript +function isValidStixId(id: string): boolean +``` + +**Parameters**: + +- `id` - String to validate as STIX ID + +**Returns**: `true` if valid STIX ID format, `false` otherwise + +**Valid STIX ID Format**: `{type}--{UUID}` where UUID is version 4 + +**Example**: + +```typescript +import { isValidStixId } from '@mitre-attack/attack-data-model'; + +console.log(isValidStixId('attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298')); // true +console.log(isValidStixId('invalid-id')); // false +console.log(isValidStixId('attack-pattern--not-a-uuid')); // false +``` + +### isValidAttackId() + +Validate ATT&CK ID format for specific object types. + +```typescript +function isValidAttackId(id: string, objectType?: string): boolean +``` + +**Parameters**: + +- `id` - String to validate as ATT&CK ID +- `objectType` - Optional STIX object type for specific validation + +**Returns**: `true` if valid ATT&CK ID format, `false` otherwise + +**Supported Formats by Object Type**: + +| Object Type | ATT&CK ID Format | Example | +|-------------|------------------|---------| +| `attack-pattern` | `T\d{4}(\.\d{3})?` | `T1055`, `T1055.001` | +| `x-mitre-tactic` | `TA\d{4}` | `TA0001` | +| `intrusion-set` | `G\d{4}` | `G0006` | +| `malware` | `S\d{4}` | `S0001` | +| `tool` | `S\d{4}` | `S0002` | +| `campaign` | `C\d{4}` | `C0001` | +| `course-of-action` | `M\d{4}` | `M1001` | + +**Example**: + +```typescript +import { isValidAttackId } from '@mitre-attack/attack-data-model'; + +// Generic validation +console.log(isValidAttackId('T1055')); // true +console.log(isValidAttackId('G0006')); // true +console.log(isValidAttackId('invalid')); // false + +// Type-specific validation +console.log(isValidAttackId('T1055.001', 'attack-pattern')); // true +console.log(isValidAttackId('G0006', 'attack-pattern')); // false +console.log(isValidAttackId('TA0001', 'x-mitre-tactic')); // true +``` + +### extractAttackId() + +Extract ATT&CK ID from an object's external references. + +```typescript +function extractAttackId(obj: AttackObject): string | null +``` + +**Parameters**: + +- `obj` - ATT&CK object with external references + +**Returns**: ATT&CK ID if found, `null` otherwise + +**Example**: + +```typescript +import { extractAttackId } from '@mitre-attack/attack-data-model'; + +const technique = attackDataModel.techniques[0]; +const attackId = extractAttackId(technique); +console.log(`Technique ID: ${attackId}`); // "T1055" +``` + +### extractStixType() + +Extract the STIX object type from a STIX ID. + +```typescript +function extractStixType(stixId: string): string | null +``` + +**Parameters**: + +- `stixId` - Valid STIX ID + +**Returns**: Object type if valid, `null` otherwise + +**Example**: + +```typescript +import { extractStixType } from '@mitre-attack/attack-data-model'; + +const type = extractStixType('attack-pattern--12345678-1234-1234-1234-123456789012'); +console.log(type); // "attack-pattern" + +const invalidType = extractStixType('invalid-id'); +console.log(invalidType); // null +``` + +## Data Analysis Utilities + +### getObjectCounts() + +Get counts of all object types in a data model. + +```typescript +function getObjectCounts(dataModel: AttackDataModel): Record +``` + +**Parameters**: + +- `dataModel` - AttackDataModel instance + +**Returns**: Object with counts by STIX object type + +**Example**: + +```typescript +import { getObjectCounts } from '@mitre-attack/attack-data-model'; + +const counts = getObjectCounts(attackDataModel); +console.log(counts); +// { +// "attack-pattern": 196, +// "x-mitre-tactic": 14, +// "intrusion-set": 142, +// "malware": 89, +// "tool": 76, +// "relationship": 2341 +// } +``` + +### findBrokenRelationships() + +Identify relationships that reference non-existent objects. + +```typescript +function findBrokenRelationships(dataModel: AttackDataModel): BrokenRelationship[] +``` + +**Returns**: Array of broken relationship information + +**BrokenRelationship Interface**: + +```typescript +interface BrokenRelationship { + relationshipId: string; + relationshipType: string; + sourceId: string; + targetId: string; + missingReference: 'source' | 'target'; + description?: string; +} +``` + +**Example**: + +```typescript +import { findBrokenRelationships } from '@mitre-attack/attack-data-model'; + +const broken = findBrokenRelationships(attackDataModel); +if (broken.length > 0) { + console.log(`Found ${broken.length} broken relationships:`); + broken.forEach(rel => { + console.log(`- ${rel.relationshipType} missing ${rel.missingReference}`); + }); +} else { + console.log('All relationships are valid'); +} +``` + +### getRelationshipCounts() + +Count relationships by type. + +```typescript +function getRelationshipCounts(dataModel: AttackDataModel): Record +``` + +**Parameters**: + +- `dataModel` - AttackDataModel instance + +**Returns**: Object with counts by relationship type + +**Example**: + +```typescript +import { getRelationshipCounts } from '@mitre-attack/attack-data-model'; + +const relCounts = getRelationshipCounts(attackDataModel); +console.log(relCounts); +// { +// "uses": 1824, +// "mitigates": 389, +// "subtechnique-of": 198, +// "attributed-to": 67, +// "detects": 156 +// } +``` + +## Filtering and Search Utilities + +### filterByPlatform() + +Filter techniques by platform. + +```typescript +function filterByPlatform( + techniques: TechniqueImpl[], + platform: string | string[] +): TechniqueImpl[] +``` + +**Parameters**: + +- `techniques` - Array of techniques to filter +- `platform` - Platform name(s) to match + +**Returns**: Filtered array of techniques + +**Example**: + +```typescript +import { filterByPlatform } from '@mitre-attack/attack-data-model'; + +const windowsTechniques = filterByPlatform( + attackDataModel.techniques, + 'Windows' +); + +const multiPlatform = filterByPlatform( + attackDataModel.techniques, + ['Windows', 'Linux'] +); + +console.log(`Windows techniques: ${windowsTechniques.length}`); +console.log(`Multi-platform techniques: ${multiPlatform.length}`); +``` + +### filterByTactic() + +Filter techniques by associated tactic. + +```typescript +function filterByTactic( + techniques: TechniqueImpl[], + tacticShortname: string | string[] +): TechniqueImpl[] +``` + +**Parameters**: + +- `techniques` - Array of techniques to filter +- `tacticShortname` - Tactic shortname(s) to match + +**Returns**: Filtered array of techniques + +**Example**: + +```typescript +import { filterByTactic } from '@mitre-attack/attack-data-model'; + +const credAccessTechniques = filterByTactic( + attackDataModel.techniques, + 'credential-access' +); + +const multiTactic = filterByTactic( + attackDataModel.techniques, + ['initial-access', 'execution'] +); + +console.log(`Credential Access techniques: ${credAccessTechniques.length}`); +``` + +### searchByName() + +Search objects by name with fuzzy matching. + +```typescript +function searchByName( + objects: T[], + searchTerm: string, + options?: SearchOptions +): T[] +``` + +**SearchOptions Interface**: + +```typescript +interface SearchOptions { + caseSensitive?: boolean; + fuzzyMatch?: boolean; + maxResults?: number; +} +``` + +**Parameters**: + +- `objects` - Array of objects to search +- `searchTerm` - Text to search for +- `options` - Search configuration + +**Returns**: Array of matching objects + +**Example**: + +```typescript +import { searchByName } from '@mitre-attack/attack-data-model'; + +// Case-insensitive exact match +const credentialTechniques = searchByName( + attackDataModel.techniques, + 'credential', + { caseSensitive: false, maxResults: 10 } +); + +// Fuzzy matching +const processMatches = searchByName( + attackDataModel.techniques, + 'proces', + { fuzzyMatch: true, maxResults: 5 } +); + +console.log(`Found ${credentialTechniques.length} credential techniques`); +``` + +## Validation Utilities + +### validateBundle() + +Validate a STIX bundle structure. + +```typescript +function validateBundle(bundle: unknown): ValidationResult +``` + +**ValidationResult Interface**: + +```typescript +interface ValidationResult { + isValid: boolean; + errors: ValidationError[]; + warnings: string[]; + objectCounts: Record; +} +``` + +**Parameters**: + +- `bundle` - Bundle data to validate + +**Returns**: Validation result with details + +**Example**: + +```typescript +import { validateBundle } from '@mitre-attack/attack-data-model'; +import fs from 'fs'; + +const bundleData = JSON.parse(fs.readFileSync('bundle.json', 'utf8')); +const result = validateBundle(bundleData); + +if (result.isValid) { + console.log('✅ Bundle is valid'); + console.log('Object counts:', result.objectCounts); +} else { + console.log('❌ Bundle validation failed'); + result.errors.forEach(error => { + console.log(`- ${error.message}`); + }); +} +``` + +### validateRelationshipIntegrity() + +Check that all relationships reference valid objects. + +```typescript +function validateRelationshipIntegrity(dataModel: AttackDataModel): IntegrityResult +``` + +**IntegrityResult Interface**: + +```typescript +interface IntegrityResult { + isValid: boolean; + brokenRelationships: BrokenRelationship[]; + orphanedObjects: string[]; + totalRelationships: number; +} +``` + +**Example**: + +```typescript +import { validateRelationshipIntegrity } from '@mitre-attack/attack-data-model'; + +const integrity = validateRelationshipIntegrity(attackDataModel); + +console.log(`Total relationships: ${integrity.totalRelationships}`); +console.log(`Broken relationships: ${integrity.brokenRelationships.length}`); +console.log(`Orphaned objects: ${integrity.orphanedObjects.length}`); + +if (integrity.isValid) { + console.log('✅ All relationships are valid'); +} else { + console.log('❌ Found integrity issues'); +} +``` + +## Export Utilities + +### exportToJson() + +Export data model to JSON format. + +```typescript +function exportToJson( + dataModel: AttackDataModel, + options?: ExportOptions +): string +``` + +**ExportOptions Interface**: + +```typescript +interface ExportOptions { + pretty?: boolean; + includeRelationships?: boolean; + objectTypes?: string[]; + minify?: boolean; +} +``` + +**Example**: + +```typescript +import { exportToJson } from '@mitre-attack/attack-data-model'; + +// Pretty-printed with all data +const fullExport = exportToJson(attackDataModel, { + pretty: true, + includeRelationships: true +}); + +// Minified with only techniques +const techniquesOnly = exportToJson(attackDataModel, { + minify: true, + objectTypes: ['attack-pattern'] +}); + +fs.writeFileSync('full-export.json', fullExport); +fs.writeFileSync('techniques.json', techniquesOnly); +``` + +### exportToCsv() + +Export specific object collections to CSV format. + +```typescript +function exportToCsv( + objects: AttackObject[], + fields?: string[] +): string +``` + +**Parameters**: + +- `objects` - Array of objects to export +- `fields` - Specific fields to include (optional) + +**Returns**: CSV-formatted string + +**Example**: + +```typescript +import { exportToCsv } from '@mitre-attack/attack-data-model'; + +// Export all technique fields +const techniquesCsv = exportToCsv(attackDataModel.techniques); + +// Export specific fields only +const basicTechniquesCsv = exportToCsv( + attackDataModel.techniques, + ['name', 'external_references', 'x_mitre_platforms'] +); + +fs.writeFileSync('techniques.csv', techniquesCsv); +fs.writeFileSync('techniques-basic.csv', basicTechniquesCsv); +``` + +--- + +## See Also + +- **[AttackDataModel](./attack-data-model)** - Main data model class +- **[DataSource](./data-sources)** - Data source configuration +- **[Error Reference](../errors)** - Error types and handling diff --git a/docusaurus/docs/reference/configuration.md b/docusaurus/docs/reference/configuration.md new file mode 100644 index 00000000..dd0aa94b --- /dev/null +++ b/docusaurus/docs/reference/configuration.md @@ -0,0 +1,526 @@ +# Configuration Reference + +**Complete configuration options for all data source types** + +This reference documents all configuration parameters available when creating `DataSource` instances and initializing the ATT&CK Data Model library. + +## DataSource Configuration + +### Base Configuration + +All data sources require these base configuration options: + +```typescript +interface DataSourceOptions { + source: 'attack' | 'file' | 'url' | 'taxii'; + parsingMode?: 'strict' | 'relaxed'; +} +``` + +#### source + +**Type**: `'attack' | 'file' | 'url' | 'taxii'` +**Required**: ✅ +**Description**: Specifies the type of data source to load + +| Value | Description | +|-------|-------------| +| `'attack'` | Official MITRE ATT&CK STIX 2.1 repository | +| `'file'` | Local STIX bundle JSON file | +| `'url'` | Remote URL serving STIX content | +| `'taxii'` | TAXII 2.1 server *(coming soon)* | + +#### parsingMode + +**Type**: `'strict' | 'relaxed'` +**Required**: ❌ +**Default**: `'strict'` +**Description**: Controls validation behavior during data parsing + +| Mode | Behavior | +|------|----------| +| `'strict'` | All objects must pass validation; failures abort loading | +| `'relaxed'` | Invalid objects are logged but skipped; loading continues | + +**Recommendations**: + +- Use `'strict'` for production applications with trusted data sources +- Use `'relaxed'` for development, testing, or experimental data + +## Attack Source Configuration + +Configuration for loading official MITRE ATT&CK data. + +```typescript +interface AttackSourceOptions extends DataSourceOptions { + source: 'attack'; + domain: 'enterprise-attack' | 'mobile-attack' | 'ics-attack'; + version?: string; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +### Attack-Specific Options + +#### domain + +**Type**: `'enterprise-attack' | 'mobile-attack' | 'ics-attack'` +**Required**: ✅ +**Description**: ATT&CK domain to load + +| Domain | Description | Object Count (Approx.) | +|--------|-------------|------------------------| +| `'enterprise-attack'` | Enterprise techniques for Windows, Linux, macOS, containers, cloud | 600+ techniques, 14 tactics | +| `'mobile-attack'` | Mobile-specific techniques for Android and iOS | 100+ techniques, 14 tactics | +| `'ics-attack'` | Industrial Control Systems techniques | 80+ techniques, 11 tactics | + +#### version + +**Type**: `string` +**Required**: ❌ +**Default**: `'latest'` +**Description**: Specific ATT&CK version or 'latest' for most recent release + +**Available Versions**: + +- `'latest'` - Always loads the most recent release +- `'15.1'` - ATT&CK v15.1 (current stable) +- `'15.0'` - ATT&CK v15.0 +- `'14.1'` - ATT&CK v14.1 +- See [ATT&CK Releases](https://github.com/mitre-attack/attack-stix-data/releases) for all versions + +**Version Selection Guidelines**: + +- **Production**: Use specific version (e.g., `'15.1'`) for stability +- **Development**: Use `'latest'` for newest features +- **Research**: Use specific version for reproducible results + +## File Source Configuration + +Configuration for loading local STIX bundle files. + +```typescript +interface FileSourceOptions extends DataSourceOptions { + source: 'file'; + file: string; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'file', + file: '/path/to/enterprise-attack.json', + parsingMode: 'relaxed' +}); +``` + +### File-Specific Options + +#### file + +**Type**: `string` +**Required**: ✅ +**Description**: Path to STIX bundle JSON file (absolute or relative) + +**Path Resolution**: + +- Relative paths are resolved from current working directory +- Use absolute paths for guaranteed location +- File must be readable by the Node.js process + +**File Requirements**: + +```json +{ + "type": "bundle", + "id": "bundle--12345678-1234-1234-1234-123456789012", + "objects": [ + // Array of STIX objects + ] +} +``` + +**Common File Locations**: + +```typescript +// Relative to project root +file: './data/custom-attack.json' + +// Absolute path +file: '/var/data/attack/enterprise.json' + +// User home directory +file: path.join(os.homedir(), 'attack-data', 'bundle.json') +``` + +## URL Source Configuration + +Configuration for loading remote STIX bundles over HTTP/HTTPS. + +```typescript +interface UrlSourceOptions extends DataSourceOptions { + source: 'url'; + url: string; + timeout?: number; + headers?: Record; + retryAttempts?: number; + retryDelay?: number; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'url', + url: 'https://api.example.com/attack/enterprise.json', + timeout: 30000, + headers: { + 'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOi...', + 'Accept': 'application/json', + 'User-Agent': 'MyApp/1.0 ATT&CK-Data-Model' + }, + retryAttempts: 3, + retryDelay: 1000, + parsingMode: 'strict' +}); +``` + +### URL-Specific Options + +#### url + +**Type**: `string` +**Required**: ✅ +**Description**: HTTP/HTTPS URL to STIX bundle resource + +**URL Requirements**: + +- Must be accessible via HTTP or HTTPS +- Should return JSON content with appropriate MIME type +- Must contain valid STIX 2.1 bundle + +#### timeout + +**Type**: `number` +**Required**: ❌ +**Default**: `10000` (10 seconds) +**Description**: Request timeout in milliseconds + +**Timeout Guidelines**: + +- **Small datasets** (< 1 MB): 10-30 seconds +- **Large datasets** (> 10 MB): 60-300 seconds +- **Slow networks**: Increase proportionally + +#### headers + +**Type**: `Record` +**Required**: ❌ +**Description**: HTTP headers for authentication and content negotiation + +**Common Headers**: + +```typescript +{ + // Authentication + 'Authorization': 'Bearer ', + 'X-API-Key': '', + + // Content negotiation + 'Accept': 'application/json', + 'Accept': 'application/stix+json', + + // User agent identification + 'User-Agent': 'MyApp/1.0 (contact@example.com)', + + // Custom headers + 'X-Custom-Header': 'value' +} +``` + +#### retryAttempts + +**Type**: `number` +**Required**: ❌ +**Default**: `3` +**Description**: Number of retry attempts on failure + +#### retryDelay + +**Type**: `number` +**Required**: ❌ +**Default**: `1000` (1 second) +**Description**: Base delay between retry attempts in milliseconds + +**Retry Logic**: Uses exponential backoff - each retry delay is multiplied by 2 + +## TAXII Source Configuration + +Configuration for loading data from TAXII 2.1 servers *(coming soon)*. + +```typescript +interface TaxiiSourceOptions extends DataSourceOptions { + source: 'taxii'; + server: string; + collection: string; + credentials?: { + username: string; + password: string; + }; + apiRoot?: string; + timeout?: number; +} +``` + +### Planned Example + +```typescript +const dataSource = new DataSource({ + source: 'taxii', + server: 'https://cti-taxii.mitre.org', + collection: 'enterprise-attack', + credentials: { + username: 'user', + password: 'pass' + }, + apiRoot: '/taxii2/', + timeout: 30000 +}); +``` + +> **Note**: TAXII source support is planned for future releases. + +## Global Configuration + +### Environment Variables + +Configure library behavior through environment variables: + +#### ATTACK_DEBUG + +**Type**: `'true' | 'false'` +**Default**: `'false'` +**Description**: Enable verbose debug logging + +```bash +export ATTACK_DEBUG=true +node app.js +``` + +#### ATTACK_CACHE_DIR + +**Type**: `string` +**Default**: `os.tmpdir()/attack-data-model` +**Description**: Directory for caching downloaded data + +```bash +export ATTACK_CACHE_DIR=/var/cache/attack-data +node app.js +``` + +#### ATTACK_CACHE_TTL + +**Type**: `number` +**Default**: `3600` (1 hour) +**Description**: Cache time-to-live in seconds + +```bash +export ATTACK_CACHE_TTL=7200 # 2 hours +node app.js +``` + +#### ATTACK_MAX_BUNDLE_SIZE + +**Type**: `number` +**Default**: `104857600` (100 MB) +**Description**: Maximum bundle size in bytes + +```bash +export ATTACK_MAX_BUNDLE_SIZE=209715200 # 200 MB +node app.js +``` + +### Programmatic Configuration + +Configure library behavior programmatically: + +```typescript +import { configure } from '@mitre-attack/attack-data-model'; + +configure({ + debug: true, + cacheDir: './data/cache', + cacheTtl: 3600, + maxBundleSize: 100 * 1024 * 1024, // 100 MB + userAgent: 'MyApp/1.0' +}); +``` + +#### Global Configuration Options + +```typescript +interface GlobalConfiguration { + debug?: boolean; + cacheDir?: string; + cacheTtl?: number; + maxBundleSize?: number; + userAgent?: string; + retryAttempts?: number; + retryDelay?: number; +} +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `debug` | `boolean` | `false` | Enable debug logging | +| `cacheDir` | `string` | OS temp dir | Cache directory path | +| `cacheTtl` | `number` | `3600` | Cache TTL in seconds | +| `maxBundleSize` | `number` | `104857600` | Max bundle size in bytes | +| `userAgent` | `string` | Library default | HTTP User-Agent header | +| `retryAttempts` | `number` | `3` | Default retry attempts | +| `retryDelay` | `number` | `1000` | Default retry delay (ms) | + +## Configuration Validation + +### Validation Rules + +The library validates configuration during `DataSource` creation: + +```typescript +// ✅ Valid configurations +const validConfigs = [ + { source: 'attack', domain: 'enterprise-attack' }, + { source: 'file', file: './data.json' }, + { source: 'url', url: 'https://example.com/data.json' } +]; + +// ❌ Invalid configurations +const invalidConfigs = [ + { source: 'attack' }, // Missing domain + { source: 'file' }, // Missing file path + { source: 'url' }, // Missing URL + { source: 'invalid' } // Invalid source type +]; +``` + +### Configuration Errors + +Common configuration errors and solutions: + +| Error | Cause | Solution | +|-------|-------|----------| +| `Missing required option: domain` | Attack source without domain | Add `domain` property | +| `Missing required option: file` | File source without path | Add `file` property | +| `Missing required option: url` | URL source without URL | Add `url` property | +| `Invalid source type` | Unknown source value | Use valid source type | +| `Invalid domain` | Unknown domain value | Use valid ATT&CK domain | + +## Best Practices + +### Production Configuration + +```typescript +// Recommended production setup +const productionSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin specific version + parsingMode: 'strict' // Enforce data quality +}); + +// With global configuration +configure({ + debug: false, // Disable debug in production + cacheDir: '/var/cache/attack', // Persistent cache location + cacheTtl: 86400, // 24 hour cache + userAgent: 'MyApp/2.1 (security-team@company.com)' +}); +``` + +### Development Configuration + +```typescript +// Recommended development setup +const devSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: 'latest', // Use latest for development + parsingMode: 'relaxed' // More forgiving validation +}); + +// With debug logging +configure({ + debug: true, // Enable detailed logging + cacheDir: './dev-cache', // Local cache directory + cacheTtl: 3600 // 1 hour cache for faster iteration +}); +``` + +### Performance Configuration + +```typescript +// For high-performance scenarios +const perfSource = new DataSource({ + source: 'url', + url: 'https://cdn.example.com/attack-data.json', + timeout: 60000, // Generous timeout + retryAttempts: 5, // More retries + retryDelay: 2000, // Longer retry delay + parsingMode: 'strict' +}); + +configure({ + maxBundleSize: 500 * 1024 * 1024, // 500 MB for large datasets + cacheTtl: 604800 // 7 day cache +}); +``` + +## Migration Guide + +### Upgrading from v1.x + +```typescript +// v1.x configuration +const oldConfig = { + dataSource: 'github', + attackVersion: '14.1', + strictMode: true +}; + +// v2.x equivalent +const newConfig = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '14.1', + parsingMode: 'strict' +}); +``` + +### Environment Variable Changes + +| v1.x Variable | v2.x Variable | Notes | +|---------------|---------------|-------| +| `ATT_DEBUG` | `ATTACK_DEBUG` | Renamed for consistency | +| `ATT_CACHE_PATH` | `ATTACK_CACHE_DIR` | Renamed and improved | +| `ATT_VERSION` | *(removed)* | Now per-DataSource configuration | + +--- + +## See Also + +- **[DataSource API](./api/data-sources)** - DataSource class documentation +- **[Error Reference](./errors)** - Configuration error troubleshooting +- **[How-to Guide: Error Handling](../how-to-guides/error-handling)** - Robust configuration patterns diff --git a/docusaurus/docs/reference/errors.md b/docusaurus/docs/reference/errors.md new file mode 100644 index 00000000..4788c1e2 --- /dev/null +++ b/docusaurus/docs/reference/errors.md @@ -0,0 +1,389 @@ +# Error Reference + +**Complete error codes, types, and resolution strategies** + +This reference documents all error types that can occur when using the ATT&CK Data Model library, along with their meanings, common causes, and recommended solutions. + +## Error Categories + +### Data Source Errors + +Errors related to data source configuration, registration, and loading. + +#### DataSourceError + +**Type**: `DataSourceError` +**Thrown by**: `registerDataSource()`, data source validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `DS001` | `"Invalid data source type: {type}"` | Unsupported source type | Use 'attack', 'file', 'url', or 'taxii' | +| `DS002` | `"Missing required option: {option}"` | Required configuration missing | Provide required option for source type | +| `DS003` | `"Data source registration failed"` | General registration failure | Check configuration and network connectivity | +| `DS004` | `"Data source not found: {uuid}"` | Invalid UUID in loadDataModel() | Use valid UUID from registerDataSource() | +| `DS005` | `"Data source already registered: {uuid}"` | Duplicate registration attempt | Use existing UUID or clear cache first | + +**Example**: + +```typescript +// ❌ Throws DS001 - Invalid data source type +const dataSource = new DataSource({ source: 'invalid' }); + +// ❌ Throws DS002 - Missing required domain +const dataSource = new DataSource({ source: 'attack' }); + +// ✅ Correct configuration +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack' +}); +``` + +#### NetworkError + +**Type**: `NetworkError` +**Thrown by**: URL and attack source loading + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `NET001` | `"Network request failed: {url}"` | Network connectivity issues | Check internet connection | +| `NET002` | `"Request timeout: {url}"` | Request exceeded timeout | Increase timeout or check server status | +| `NET003` | `"Authentication failed: {url}"` | Invalid credentials | Verify authentication details | +| `NET004` | `"Resource not found: {url}"` | 404 or invalid URL | Verify URL exists and is accessible | +| `NET005` | `"Server error: {status} {url}"` | Server-side error (5xx) | Check server status or try again later | + +**Example**: + +```typescript +try { + const dataSource = new DataSource({ + source: 'url', + url: 'https://invalid-url.example/data.json', + timeout: 5000 + }); + await registerDataSource(dataSource); +} catch (error) { + if (error.code === 'NET002') { + // Increase timeout and retry + dataSource.timeout = 30000; + } +} +``` + +#### FileSystemError + +**Type**: `FileSystemError` +**Thrown by**: File source loading + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `FS001` | `"File not found: {path}"` | File doesn't exist | Verify file path is correct | +| `FS002` | `"Permission denied: {path}"` | Insufficient file permissions | Check file permissions | +| `FS003` | `"File is not readable: {path}"` | File exists but can't be read | Ensure file is readable | +| `FS004` | `"Invalid file format: {path}"` | File is not valid JSON | Verify file contains valid JSON | + +### Validation Errors + +Errors from schema validation and data parsing. + +#### ValidationError + +**Type**: `ValidationError` (extends `ZodError`) +**Thrown by**: Schema parsing, object validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `VAL001` | `"Required field missing: {field}"` | Required property not provided | Add missing field to object | +| `VAL002` | `"Invalid field type: {field}"` | Wrong data type for field | Use correct type (string, number, array, etc.) | +| `VAL003` | `"Invalid field value: {field}"` | Value doesn't match constraints | Check field constraints and valid values | +| `VAL004` | `"Invalid STIX ID format: {id}"` | Malformed STIX identifier | Use format: `{type}--{uuid}` | +| `VAL005` | `"Invalid ATT&CK ID format: {id}"` | Malformed ATT&CK identifier | Follow ATT&CK ID patterns (T1234, G0001, etc.) | +| `VAL006` | `"Schema refinement failed: {details}"` | Custom validation rule failed | Check object meets all requirements | + +**Common Validation Issues**: + +```typescript +// ❌ VAL001 - Missing required field +{ + "type": "attack-pattern", + "id": "attack-pattern--12345...", + // Missing: name, description, created, modified, etc. +} + +// ❌ VAL002 - Invalid field type +{ + "type": "attack-pattern", + "name": 123, // Should be string + "x_mitre_platforms": "Windows" // Should be array +} + +// ❌ VAL004 - Invalid STIX ID +{ + "id": "not-a-valid-stix-id" +} + +// ✅ Valid object +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "spec_version": "2.1", + "created": "2023-01-01T00:00:00.000Z", + "modified": "2023-01-01T00:00:00.000Z", + "name": "Process Injection", + "description": "Adversaries may inject code...", + "x_mitre_attack_spec_version": "3.3.0", + "x_mitre_version": "1.0" +} +``` + +#### BundleValidationError + +**Type**: `BundleValidationError` +**Thrown by**: STIX bundle validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `BUN001` | `"Invalid bundle structure"` | Not a valid STIX bundle | Ensure `type: "bundle"` and objects array | +| `BUN002` | `"Bundle contains no objects"` | Empty objects array | Add STIX objects to bundle | +| `BUN003` | `"Duplicate object IDs in bundle"` | Same ID used multiple times | Ensure all object IDs are unique | +| `BUN004` | `"Invalid bundle ID format"` | Malformed bundle identifier | Use format: `bundle--{uuid}` | + +### Relationship Errors + +Errors from relationship processing and navigation. + +#### RelationshipError + +**Type**: `RelationshipError` +**Thrown by**: Relationship processing, navigation methods + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `REL001` | `"Invalid relationship type: {type}"` | Unknown relationship type | Use valid STIX relationship types | +| `REL002` | `"Source object not found: {id}"` | Relationship source doesn't exist | Ensure source object is in dataset | +| `REL003` | `"Target object not found: {id}"` | Relationship target doesn't exist | Ensure target object is in dataset | +| `REL004` | `"Circular relationship detected"` | Self-referencing relationship chain | Fix relationship structure | +| `REL005` | `"Invalid relationship direction"` | Wrong source/target for relationship type | Check relationship type requirements | + +**Example**: + +```typescript +// ❌ REL001 - Invalid relationship type +{ + "type": "relationship", + "relationship_type": "invalid-relationship", + "source_ref": "attack-pattern--...", + "target_ref": "x-mitre-tactic--..." +} + +// ✅ Valid relationship +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "intrusion-set--...", + "target_ref": "attack-pattern--..." +} +``` + +### Parsing Errors + +Errors from data parsing and processing. + +#### ParsingError + +**Type**: `ParsingError` +**Thrown by**: Data parsing, format processing + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `PAR001` | `"Invalid JSON format"` | Malformed JSON data | Fix JSON syntax errors | +| `PAR002` | `"Unsupported data format"` | Non-JSON or unknown format | Use valid STIX JSON format | +| `PAR003` | `"Data too large to process"` | Bundle exceeds size limits | Split into smaller bundles or increase limits | +| `PAR004` | `"Encoding error: {encoding}"` | Unsupported character encoding | Use UTF-8 encoding | + +### Configuration Errors + +Errors from library configuration and setup. + +#### ConfigurationError + +**Type**: `ConfigurationError` +**Thrown by**: Library initialization, configuration validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `CFG001` | `"Invalid configuration: {details}"` | Configuration object invalid | Check configuration structure | +| `CFG002` | `"Conflicting options: {options}"` | Mutually exclusive options set | Use only one of the conflicting options | +| `CFG003` | `"Environment not supported: {env}"` | Unsupported runtime environment | Use supported Node.js version | +| `CFG004` | `"Missing dependency: {dependency}"` | Required dependency not installed | Install missing dependencies | + +## Error Handling Patterns + +### Basic Error Handling + +```typescript +try { + const dataSource = new DataSource({ source: 'attack', domain: 'enterprise-attack' }); + const uuid = await registerDataSource(dataSource); + const attackDataModel = loadDataModel(uuid); +} catch (error) { + if (error instanceof ValidationError) { + console.error('Validation failed:', error.errors); + } else if (error instanceof NetworkError) { + console.error('Network issue:', error.message); + } else if (error instanceof FileSystemError) { + console.error('File system error:', error.message); + } else { + console.error('Unknown error:', error); + } +} +``` + +### Specific Error Code Handling + +```typescript +try { + // ... data loading code +} catch (error) { + switch (error.code) { + case 'NET002': // Request timeout + console.log('Request timed out, retrying with longer timeout...'); + // Implement retry logic + break; + + case 'VAL001': // Missing required field + console.error('Data validation failed - required field missing'); + // Log specific validation errors + break; + + case 'REL002': // Relationship target not found + console.warn('Relationship integrity issue - continuing in relaxed mode'); + // Use relaxed parsing mode + break; + + default: + console.error('Unhandled error:', error); + } +} +``` + +### Comprehensive Error Handler + +```typescript +class AttackErrorHandler { + static handle(error: unknown): { + severity: 'fatal' | 'warning' | 'info'; + message: string; + solution: string; + } { + if (error instanceof ValidationError) { + return { + severity: 'fatal', + message: `Validation failed: ${error.errors.length} errors`, + solution: 'Fix validation errors or use relaxed parsing mode' + }; + } + + if (error instanceof NetworkError) { + return { + severity: error.code === 'NET002' ? 'warning' : 'fatal', + message: `Network error: ${error.message}`, + solution: 'Check connectivity and retry with backoff' + }; + } + + if (error instanceof RelationshipError) { + return { + severity: 'warning', + message: `Relationship issue: ${error.message}`, + solution: 'Use relaxed mode or fix relationship integrity' + }; + } + + return { + severity: 'fatal', + message: `Unknown error: ${error}`, + solution: 'Check error details and library documentation' + }; + } +} + +// Usage +try { + // ... ATT&CK data operations +} catch (error) { + const errorInfo = AttackErrorHandler.handle(error); + console.log(`[${errorInfo.severity.toUpperCase()}] ${errorInfo.message}`); + console.log(`Solution: ${errorInfo.solution}`); +} +``` + +## Debugging Tips + +### Enable Detailed Logging + +```typescript +// Set environment variable for verbose logging +process.env.ATTACK_DEBUG = 'true'; + +// Or enable programmatically +import { setDebugMode } from '@mitre-attack/attack-data-model'; +setDebugMode(true); +``` + +### Validate Data Before Processing + +```typescript +import { validateBundle } from '@mitre-attack/attack-data-model'; + +// Validate bundle structure before registration +const result = validateBundle(bundleData); +if (!result.isValid) { + console.error('Bundle validation failed:'); + result.errors.forEach(error => console.error(`- ${error.message}`)); +} else { + // Safe to proceed with registration +} +``` + +### Check Data Source Status + +```typescript +import { getDataSourceStatus } from '@mitre-attack/attack-data-model'; + +const status = getDataSourceStatus(uuid); +console.log(`Data source status: ${status.state}`); +console.log(`Last updated: ${status.lastUpdated}`); +console.log(`Object counts:`, status.objectCounts); +``` + +## Recovery Strategies + +### Network Failures + +1. **Implement exponential backoff retry** +2. **Use cached data if available** +3. **Fall back to local data sources** +4. **Increase timeout values for slow connections** + +### Validation Failures + +1. **Use relaxed parsing mode for development** +2. **Fix specific validation errors identified** +3. **Update data to match current schema requirements** +4. **Use partial data loading if some objects are valid** + +### Relationship Issues + +1. **Use relaxed mode to ignore broken relationships** +2. **Validate relationship integrity before processing** +3. **Remove orphaned relationships from datasets** +4. **Update data sources to fix reference issues** + +--- + +## See Also + +- **[Configuration Reference](./configuration)** - Complete configuration options +- **[How-to Guide: Error Handling](../how-to-guides/error-handling)** - Practical error handling strategies +- **[Validation Guide](../how-to-guides/validate-bundles)** - Data validation techniques diff --git a/docusaurus/docs/reference/index.md b/docusaurus/docs/reference/index.md new file mode 100644 index 00000000..313c3c68 --- /dev/null +++ b/docusaurus/docs/reference/index.md @@ -0,0 +1,190 @@ +# Reference + +**Complete technical specifications and API documentation** + +This section provides comprehensive reference material for the ATT&CK Data Model library. All classes, methods, schemas, and configuration options are documented here with precise technical details. + +## API Documentation + +### Core Classes + +- **[AttackDataModel](./api/attack-data-model)** - Main data model containing all ATT&CK objects +- **[DataSource](./api/data-sources)** - Data source configuration and registration +- **[Utility Functions](./api/utilities)** - Helper functions and data manipulation tools + +### Implementation Classes (SDO) + +All STIX Domain Object implementations with relationship navigation methods: + +| Class | ATT&CK Object | Key Methods | +|-------|---------------|-------------| +| `TechniqueImpl` | Techniques | `getTactics()`, `getSubtechniques()`, `getParentTechnique()`, `getMitigations()` | +| `TacticImpl` | Tactics | `getTechniques()` | +| `GroupImpl` | Groups/Intrusion Sets | `getTechniques()`, `getAssociatedSoftware()`, `getAssociatedCampaigns()` | +| `CampaignImpl` | Campaigns | `getTechniques()`, `getSoftware()`, `getAttributedTo()` | +| `MalwareImpl` | Malware | `getTechniques()`, `getAssociatedGroups()` | +| `ToolImpl` | Tools | `getTechniques()`, `getAssociatedGroups()` | +| `MitigationImpl` | Mitigations | `getTechniques()` | + +## Schema Documentation + +### STIX Domain Objects (SDO) + +Auto-generated documentation for all ATT&CK object schemas: + +- **[Techniques](./schemas/sdo/technique.schema)** - Attack patterns including sub-techniques +- **[Tactics](./schemas/sdo/tactic.schema)** - Adversary tactical goals +- **[Groups](./schemas/sdo/group.schema)** - Threat actor groups and intrusion sets +- **[Malware](./schemas/sdo/malware.schema)** - Malicious software +- **[Tools](./schemas/sdo/tool.schema)** - Legitimate software used by adversaries +- **[Campaigns](./schemas/sdo/campaign.schema)** - Coordinated attack campaigns +- **[Mitigations](./schemas/sdo/mitigation.schema)** - Defensive measures and controls + +### STIX Relationship Objects (SRO) + +- **[Relationships](./schemas/sro/relationship.schema)** - All relationship types and their constraints + +### STIX Meta Objects (SMO) + +- **[Marking Definitions](./schemas/smo/marking-definition.schema)** - Data marking and sharing constraints + +### Additional Object Types + +- **[Data Sources](./schemas/sdo/data-source.schema)** - Detection data categories +- **[Data Components](./schemas/sdo/data-component.schema)** - Specific detection data types +- **[Analytics](./schemas/sdo/analytic.schema)** - Detection analytics and rules +- **[Assets](./schemas/sdo/asset.schema)** - Infrastructure and system assets + +## Configuration Reference + +### Data Source Options + +Complete configuration parameters for all data source types: + +| Source Type | Configuration | Description | +|-------------|---------------|-------------| +| `attack` | `domain`, `version` | Official MITRE ATT&CK repository | +| `file` | `file`, `parsingMode` | Local STIX bundle files | +| `url` | `url`, `parsingMode` | Remote STIX bundle URLs | +| `taxii` | `server`, `collection`, `credentials` | TAXII 2.1 servers *(coming soon)* | + +**[View complete configuration reference →](./configuration)** + +## Error Reference + +### Error Types and Handling + +Comprehensive error codes, meanings, and resolution strategies: + +| Error Type | Description | Common Causes | +|------------|-------------|---------------| +| `ValidationError` | Schema validation failures | Invalid STIX data, missing required fields | +| `DataSourceError` | Data source access issues | Network failures, file not found, authentication | +| `RelationshipError` | Broken object relationships | Missing target objects, invalid references | +| `ParsingError` | Data parsing failures | Malformed JSON, unsupported formats | + +**[View complete error reference →](./errors)** + +## Type Definitions + +### TypeScript Interfaces + +All exported TypeScript types and interfaces: + +```typescript +// Core types +import type { + AttackDataModel, + DataSource, + DataSourceOptions, + ParsingMode +} from '@mitre-attack/attack-data-model'; + +// Schema types +import type { + Technique, + Tactic, + Group, + Campaign, + Malware, + Tool, + Mitigation, + Relationship +} from '@mitre-attack/attack-data-model'; + +// Implementation class types +import type { + TechniqueImpl, + TacticImpl, + GroupImpl, + CampaignImpl, + MalwareImpl, + ToolImpl, + MitigationImpl +} from '@mitre-attack/attack-data-model'; +``` + +## Version Compatibility + +### ATT&CK Specification Versions + +- **Current**: 3.3.0 +- **Supported**: 3.0.0+ +- **Deprecated**: 2.x (legacy support only) + +### Library Versions + +- **Node.js**: 20.0.0+ +- **TypeScript**: 4.5.0+ +- **Zod**: 3.20.0+ + +**[View complete compatibility matrix →](../explanation/compatibility)** + +## Quick Reference + +### Essential Imports + +```typescript +// Main entry points +import { registerDataSource, loadDataModel } from '@mitre-attack/attack-data-model'; + +// Classes +import { DataSource, AttackDataModel } from '@mitre-attack/attack-data-model'; + +// Schemas +import { techniqueSchema, tacticSchema } from '@mitre-attack/attack-data-model'; + +// Types +import type { Technique, Tactic } from '@mitre-attack/attack-data-model'; +``` + +### Common Patterns + +```typescript +// Load ATT&CK data +const dataSource = new DataSource({ source: 'attack', domain: 'enterprise-attack' }); +const uuid = await registerDataSource(dataSource); +const attackDataModel = loadDataModel(uuid); + +// Validate data +const validTechnique = techniqueSchema.parse(techniqueData); + +// Navigate relationships +const tactics = technique.getTactics(); +const subtechniques = technique.getSubtechniques(); +``` + +## Reference Usage + +This reference documentation follows these principles: + +- **Complete**: Every public API method and property is documented +- **Precise**: Exact parameter types, return values, and constraints +- **Systematic**: Consistent organization and formatting +- **Current**: Auto-generated from source code when possible + +**Looking for something specific?** Use the search functionality or check the relevant section above. + +--- + +**Need more context?** Visit the [Explanation](../explanation/) section for design rationale and architectural details. diff --git a/docusaurus/docs/reference/schemas/.gitkeep b/docusaurus/docs/reference/schemas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docusaurus/docs/sdo/stix-bundle.schema.md b/docusaurus/docs/sdo/stix-bundle.schema.md index e2dc84ba..c1c6fea7 100644 --- a/docusaurus/docs/sdo/stix-bundle.schema.md +++ b/docusaurus/docs/sdo/stix-bundle.schema.md @@ -15,4 +15,4 @@ _(\*) Required._ ## AttackObjects -_Array of at least 1 [Asset](/docs/sdo/asset.schema) | [Campaign](/docs/sdo/campaign.schema) | [Collection](/docs/sdo/collection.schema) | [DataComponent](/docs/sdo/data-component.schema) | [DataSource](/docs/sdo/data-source.schema) | [Group](/docs/sdo/group.schema) | [Identity](/docs/sdo/identity.schema) | [Malware](/docs/sdo/malware.schema) | [Matrix](/docs/sdo/matrix.schema) | [Mitigation](/docs/sdo/mitigation.schema) | [Tactic](/docs/sdo/tactic.schema) | [Technique](/docs/sdo/technique.schema) | [Tool](/docs/sdo/tool.schema) | [MarkingDefinition](/docs/smo/marking-definition.schema) | [Relationship](/docs/sro/relationship.schema) objects._ +_Array of at least 1 [Asset](../reference/schemas/sdo/asset.schema) | [Campaign](../reference/schemas/sdo/campaign.schema) | [Collection](../reference/schemas/sdo/collection.schema) | [DataComponent](../reference/schemas/sdo/data-component.schema) | [DataSource](../reference/schemas/sdo/data-source.schema) | [Group](../reference/schemas/sdo/group.schema) | [Identity](../reference/schemas/sdo/identity.schema) | [Malware](../reference/schemas/sdo/malware.schema) | [Matrix](../reference/schemas/sdo/matrix.schema) | [Mitigation](../reference/schemas/sdo/mitigation.schema) | [Tactic](../reference/schemas/sdo/tactic.schema) | [Technique](../reference/schemas/sdo/technique.schema) | [Tool](../reference/schemas/sdo/tool.schema) | [MarkingDefinition](../reference/schemas/smo/marking-definition.schema) | [Relationship](../reference/schemas/sro/relationship.schema) objects._ diff --git a/docusaurus/docs/tutorials/index.md b/docusaurus/docs/tutorials/index.md new file mode 100644 index 00000000..ef4de03e --- /dev/null +++ b/docusaurus/docs/tutorials/index.md @@ -0,0 +1,52 @@ +# Tutorials + +**Learning-oriented guides for getting started with the ATT&CK Data Model** + +Welcome to the tutorials section! These step-by-step guides will teach you how to work with the MITRE ATT&CK® Data Model (ADM) from the ground up. Each tutorial is designed to be completed by anyone with basic TypeScript knowledge, regardless of their familiarity with ATT&CK. + +## What You'll Learn + +These tutorials will take you from zero knowledge to confidently using the ATT&CK Data Model in your applications: + +1. **[Your First ATT&CK Query](./your-first-query)** - Learn the basics of loading and exploring ATT&CK data +2. **[Building a Technique Browser](./technique-browser)** - Create a complete application that browses ATT&CK techniques +3. **[Understanding ATT&CK Relationships](./relationships)** - Master navigation between related ATT&CK objects + +## Tutorial Philosophy + +These tutorials follow a hands-on approach where you'll learn by doing: + +- **Step-by-step progression**: Each tutorial builds on the previous one +- **Complete examples**: All code examples are tested and working +- **Immediate results**: Every step produces visible outcomes +- **Beginner-friendly**: No prior ATT&CK knowledge assumed + +## Prerequisites + +Before starting these tutorials, you should have: + +- **Node.js 16+** installed on your system +- **Basic TypeScript/JavaScript** knowledge (variables, functions, async/await) +- **Terminal/command line** familiarity +- A **text editor or IDE** of your choice + +## Getting Help + +If you get stuck while following a tutorial: + +1. **Double-check each step** - make sure you haven't missed anything +2. **Check the [reference documentation](../reference/)** for detailed API information +3. **Look at the [how-to guides](../how-to-guides/)** for solutions to specific problems +4. **Review the [examples](https://github.com/mitre-attack/attack-data-model/tree/main/examples)** for additional code samples + +## What's Next? + +After completing these tutorials, you'll be ready to: + +- Solve specific problems with our **[How-to Guides](../how-to-guides/)** +- Explore comprehensive API details in the **[Reference](../reference/)** section +- Understand design decisions in the **[Explanation](../explanation/)** section + +--- + +**Ready to get started?** Begin with **[Your First ATT&CK Query](./your-first-query)** to load your first ATT&CK dataset! diff --git a/docusaurus/docs/tutorials/multi-domain-analysis.md b/docusaurus/docs/tutorials/multi-domain-analysis.md new file mode 100644 index 00000000..69232e53 --- /dev/null +++ b/docusaurus/docs/tutorials/multi-domain-analysis.md @@ -0,0 +1,282 @@ +# Multi-Domain ATT&CK Analysis + +**Learn to work with Enterprise, Mobile, and ICS ATT&CK domains** + +In this tutorial, you'll learn how to load and analyze data from multiple ATT&CK domains (Enterprise, Mobile, and ICS). You'll compare techniques across domains and understand how adversary tactics vary between different environments. + +## What You'll Build + +You'll create a Node.js script that: + +- Loads data from all three ATT&CK domains +- Compares technique coverage across domains +- Identifies common and domain-specific tactics +- Demonstrates cross-domain analysis patterns + +## Prerequisites + +- Completed the [Your First ATT&CK Query](./your-first-query) tutorial +- Basic understanding of ATT&CK domains (Enterprise, Mobile, ICS) + +## Step 1: Set Up Multi-Domain Project + +Create a new directory for your multi-domain analysis: + +```bash +mkdir multi-domain-analysis +cd multi-domain-analysis +npm init -y +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +## Step 2: Load Multiple ATT&CK Domains + +Create a file named `multi-domain.ts` and add the following code: + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +async function loadAllDomains() { + console.log('🌐 Loading all ATT&CK domains...\n'); + + // Define all three domains + const domains = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + const dataModels: { [key: string]: any } = {}; + + for (const domain of domains) { + try { + console.log(`📡 Loading ${domain.label} domain...`); + + const dataSource = new DataSource({ + source: 'attack', + domain: domain.name, + version: '15.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + dataModels[domain.name] = loadDataModel(uuid); + const techniqueCount = dataModels[domain.name].techniques.length; + console.log(`✅ ${domain.label}: ${techniqueCount} techniques loaded\n`); + } + + } catch (error) { + console.error(`❌ Failed to load ${domain.label} domain:`, error); + } + } + + return dataModels; +} +``` + +## Step 3: Compare Domain Statistics + +Add this function to analyze differences between domains: + +```typescript +function analyzeDomainStatistics(dataModels: { [key: string]: any }) { + console.log('📊 Domain Comparison:\n'); + + const stats: { [key: string]: any } = {}; + + Object.keys(dataModels).forEach(domain => { + const model = dataModels[domain]; + stats[domain] = { + techniques: model.techniques.length, + tactics: model.tactics.length, + groups: model.groups.length, + software: model.malware.length + model.tools.length, + mitigations: model.mitigations.length + }; + }); + + // Display comparison table + console.log('| Metric | Enterprise | Mobile | ICS |'); + console.log('|-------------|------------|--------|-----|'); + + const metrics = ['techniques', 'tactics', 'groups', 'software', 'mitigations']; + + metrics.forEach(metric => { + const enterprise = stats['enterprise-attack']?.[metric] || 0; + const mobile = stats['mobile-attack']?.[metric] || 0; + const ics = stats['ics-attack']?.[metric] || 0; + + console.log(`| ${metric.padEnd(11)} | ${String(enterprise).padEnd(10)} | ${String(mobile).padEnd(6)} | ${String(ics).padEnd(3)} |`); + }); + + console.log('\n'); + return stats; +} +``` + +## Step 4: Find Common Tactics Across Domains + +Add this function to identify tactics that appear in multiple domains: + +```typescript +function findCommonTactics(dataModels: { [key: string]: any }) { + console.log('🎯 Tactic Analysis:\n'); + + const tacticsByDomain: { [key: string]: Set } = {}; + + // Collect tactics from each domain + Object.keys(dataModels).forEach(domain => { + tacticsByDomain[domain] = new Set(); + dataModels[domain].tactics.forEach((tactic: any) => { + tacticsByDomain[domain].add(tactic.x_mitre_shortname); + }); + }); + + // Find common tactics + const allTactics = new Set(); + Object.values(tacticsByDomain).forEach(domainTactics => { + domainTactics.forEach(tactic => allTactics.add(tactic)); + }); + + console.log('Common tactics across domains:'); + + allTactics.forEach(tactic => { + const domains = Object.keys(tacticsByDomain).filter(domain => + tacticsByDomain[domain].has(tactic) + ); + + if (domains.length > 1) { + const domainLabels = domains.map(d => { + switch(d) { + case 'enterprise-attack': return 'Enterprise'; + case 'mobile-attack': return 'Mobile'; + case 'ics-attack': return 'ICS'; + default: return d; + } + }).join(', '); + + console.log(`- ${tactic}: ${domainLabels}`); + } + }); + + console.log('\n'); +} +``` + +## Step 5: Analyze Cross-Domain Techniques + +Add this function to find techniques that may be related across domains: + +```typescript +function analyzeCrossDomainTechniques(dataModels: { [key: string]: any }) { + console.log('🔍 Cross-Domain Technique Analysis:\n'); + + // Look for techniques with similar names across domains + const enterpriseTechniques = dataModels['enterprise-attack']?.techniques || []; + const mobileTechniques = dataModels['mobile-attack']?.techniques || []; + + console.log('Similar techniques between Enterprise and Mobile:'); + + let similarCount = 0; + + enterpriseTechniques.forEach((entTechnique: any) => { + mobileTechniques.forEach((mobTechnique: any) => { + // Simple name similarity check + const entName = entTechnique.name.toLowerCase(); + const mobName = mobTechnique.name.toLowerCase(); + + if (entName === mobName) { + console.log(`📱 ${entTechnique.name}`); + console.log(` Enterprise: ${entTechnique.external_references[0].external_id}`); + console.log(` Mobile: ${mobTechnique.external_references[0].external_id}\n`); + similarCount++; + } + }); + }); + + console.log(`Found ${similarCount} techniques with identical names across domains.\n`); +} +``` + +## Step 6: Create the Main Analysis Function + +Add the main function to orchestrate the analysis: + +```typescript +async function performMultiDomainAnalysis() { + try { + // Load all domains + const dataModels = await loadAllDomains(); + + if (Object.keys(dataModels).length === 0) { + console.error('❌ No domains loaded successfully'); + return; + } + + // Perform various analyses + analyzeDomainStatistics(dataModels); + findCommonTactics(dataModels); + analyzeCrossDomainTechniques(dataModels); + + console.log('✅ Multi-domain analysis complete!'); + + } catch (error) { + console.error('❌ Analysis failed:', error); + } +} + +// Run the analysis +performMultiDomainAnalysis(); +``` + +## Step 7: Run Your Multi-Domain Analysis + +Execute your script to see the cross-domain analysis: + +```bash +npx tsx multi-domain.ts +``` + +You should see output comparing the domains, identifying common tactics, and highlighting similar techniques. + +## What You've Learned + +In this tutorial, you've learned: + +1. **How to load multiple ATT&CK domains** simultaneously +2. **How to compare domain statistics** like technique and tactic counts +3. **How to identify common patterns** across different attack environments +4. **How to analyze relationships** between Enterprise, Mobile, and ICS domains +5. **How to structure cross-domain analysis** for comprehensive threat intelligence + +## Understanding Multi-Domain ATT&CK + +Key insights from multi-domain analysis: + +- **Enterprise Domain**: Focuses on traditional IT environments with the most comprehensive technique coverage +- **Mobile Domain**: Specialized for mobile device threats with unique tactics like "Initial Access" through app stores +- **ICS Domain**: Covers industrial control systems with tactics specific to operational technology +- **Common Tactics**: Many fundamental tactics like "Execution" and "Persistence" appear across domains +- **Technique Overlap**: Some techniques have similar names but different implementations across domains + +## Next Steps + +Now that you understand cross-domain analysis, explore: + +- **[Understanding Relationships](./relationships)** - Master ATT&CK object connections +- **[How-to: Handle Performance at Scale](../how-to-guides/performance)** - Optimize for large datasets +- **[Explanation: Why ADM Exists](../explanation/why-adm-exists)** - Understand the project's design rationale + +## Common Issues + +**Memory usage**: Loading all domains simultaneously can use significant memory. Consider loading domains individually for analysis if you encounter memory limits. + +**Network timeouts**: Loading multiple domains requires multiple network requests. Ensure stable internet connectivity. + +**Version consistency**: Use the same version number across all domains to ensure compatibility in cross-domain comparisons. + +--- + +**Excellent work!** You now understand how to work with multiple ATT&CK domains and can perform comprehensive cross-domain threat analysis. diff --git a/docusaurus/docs/tutorials/relationships.md b/docusaurus/docs/tutorials/relationships.md new file mode 100644 index 00000000..82d718a1 --- /dev/null +++ b/docusaurus/docs/tutorials/relationships.md @@ -0,0 +1,431 @@ +# Understanding ATT&CK Relationships + +**Master navigation between related ATT&CK objects** + +ATT&CK's power comes from the rich relationships between techniques, tactics, groups, software, and mitigations. In this tutorial, you'll learn to navigate these connections to uncover threat intelligence insights and build comprehensive security analysis capabilities. + +## What You'll Learn + +By the end of this tutorial, you'll understand how to: + +- Navigate technique-to-tactic relationships +- Explore group-to-technique connections (procedures) +- Discover software usage patterns +- Find mitigations for specific techniques +- Work with parent/sub-technique hierarchies +- Trace transitive relationships across object types + +## Prerequisites + +Complete the previous tutorials: + +- **[Your First ATT&CK Query](./your-first-query)** +- **[Building a Technique Browser](./technique-browser)** + +## Step 1: Set Up Your Relationship Explorer + +Create a new project: + +```bash +mkdir attack-relationships +cd attack-relationships +npm init -y +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +Create `relationship-explorer.ts`: + +```typescript +import { registerDataSource, loadDataModel, DataSource, AttackDataModel } from '@mitre-attack/attack-data-model'; + +class RelationshipExplorer { + private attackDataModel: AttackDataModel; + + async initialize(): Promise { + console.log('🔗 Initializing ATT&CK Relationship Explorer...\n'); + + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (!uuid) throw new Error('Failed to register data source'); + + this.attackDataModel = loadDataModel(uuid); + console.log('✅ Data loaded successfully!\n'); + } + + // We'll add exploration methods here... +} + +// Initialize and run examples +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // We'll add example calls here... +} + +main().catch(console.error); +``` + +## Step 2: Explore Technique-Tactic Relationships + +Add this method to understand how techniques map to tactical goals: + +```typescript + exploreTechniqueTactics(): void { + console.log('🎯 TECHNIQUE-TACTIC RELATIONSHIPS\n'); + + // Find a specific technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' + ); + + if (!technique) return; + + console.log(`📋 Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`📝 Description: ${technique.description.substring(0, 100)}...\n`); + + // Get associated tactics + const tactics = technique.getTactics(); + console.log(`🎯 This technique is used for ${tactics.length} tactical goal(s):`); + + tactics.forEach((tactic, index) => { + console.log(`${index + 1}. ${tactic.name}`); + console.log(` Purpose: ${tactic.description.substring(0, 80)}...\n`); + }); + + console.log('💡 This shows how one technique can serve multiple adversary goals!\n'); + } +``` + +Add the method call to your main function: + +```typescript + // In main function: + await explorer.exploreTechniqueTactics(); +``` + +## Step 3: Discover Group-Technique Relationships (Procedures) + +Add this method to explore how groups use techniques: + +```typescript + exploreGroupTechniques(): void { + console.log('👥 GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); + + // Find APT1 group + const apt1 = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' + ); + + if (!apt1) return; + + console.log(`🏴 Group: ${apt1.name} (${apt1.external_references[0].external_id})`); + console.log(`📝 Description: ${apt1.description.substring(0, 120)}...\n`); + + // Get techniques used by this group + const techniques = apt1.getTechniques(); + console.log(`⚔️ This group uses ${techniques.length} different techniques:`); + + // Show first 5 techniques + techniques.slice(0, 5).forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Find the relationship to get procedure details + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === apt1.id && + rel.target_ref === technique.id && + rel.relationship_type === 'uses' + ); + + if (relationship && relationship.description) { + console.log(` Procedure: ${relationship.description.substring(0, 100)}...`); + } + console.log(''); + }); + + console.log(`... and ${techniques.length - 5} more techniques\n`); + } +``` + +## Step 4: Explore Software Usage Patterns + +Add this method to understand software-technique relationships: + +```typescript + exploreSoftwareUsage(): void { + console.log('💻 SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); + + // Find Mimikatz (a well-known tool) + const mimikatz = this.attackDataModel.tools.find(tool => + tool.name.toLowerCase().includes('mimikatz') + ); + + if (!mimikatz) return; + + console.log(`🔧 Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); + console.log(`📝 Description: ${mimikatz.description.substring(0, 120)}...\n`); + + // Get techniques used by this software + const techniques = mimikatz.getTechniques(); + console.log(`⚡ This tool implements ${techniques.length} techniques:`); + + techniques.forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show which tactics this technique supports + const tactics = technique.getTactics(); + console.log(` Supports tactics: ${tactics.map(t => t.name).join(', ')}\n`); + }); + + // Find groups that use this software + const groupsUsingMimikatz = this.attackDataModel.groups.filter(group => + group.getAssociatedSoftware().some(software => software.id === mimikatz.id) + ); + + console.log(`👥 This tool is used by ${groupsUsingMimikatz.length} groups:`); + groupsUsingMimikatz.slice(0, 3).forEach((group, index) => { + console.log(`${index + 1}. ${group.name} (${group.external_references[0].external_id})`); + }); + console.log(''); + } +``` + +## Step 5: Navigate Parent-Child Technique Relationships + +Add this method to explore sub-technique hierarchies: + +```typescript + exploreSubtechniqueRelationships(): void { + console.log('🌳 PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); + + // Find a parent technique with sub-techniques + const parentTechnique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1003' && + !t.x_mitre_is_subtechnique + ); + + if (!parentTechnique) return; + + console.log(`👨‍👧‍👦 Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); + console.log(`📝 Description: ${parentTechnique.description.substring(0, 120)}...\n`); + + // Get sub-techniques + const subTechniques = parentTechnique.getSubtechniques(); + console.log(`🌿 This technique has ${subTechniques.length} sub-techniques:`); + + subTechniques.forEach((subTech, index) => { + console.log(`${index + 1}. ${subTech.name} (${subTech.external_references[0].external_id})`); + console.log(` Platforms: ${subTech.x_mitre_platforms?.join(', ') || 'Not specified'}\n`); + }); + + // Navigate back from sub-technique to parent + if (subTechniques.length > 0) { + const firstSubTech = subTechniques[0]; + const parentFromChild = firstSubTech.getParentTechnique(); + + console.log(`🔄 Navigation verification:`); + console.log(`Sub-technique "${firstSubTech.name}" → Parent: "${parentFromChild?.name}"`); + console.log(`✅ Bidirectional navigation works!\n`); + } + } +``` + +## Step 6: Discover Mitigation Relationships + +Add this method to find defensive measures: + +```typescript + exploreMitigationRelationships(): void { + console.log('🛡️ MITIGATION-TECHNIQUE RELATIONSHIPS\n'); + + // Find a technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1059' + ); + + if (!technique) return; + + console.log(`⚔️ Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`📝 Description: ${technique.description.substring(0, 120)}...\n`); + + // Get mitigations for this technique + const mitigations = technique.getMitigations(); + console.log(`🛡️ This technique can be mitigated by ${mitigations.length} measures:`); + + mitigations.forEach((mitigation, index) => { + console.log(`${index + 1}. ${mitigation.name} (${mitigation.external_references[0].external_id})`); + + // Find the relationship to get mitigation guidance + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === mitigation.id && + rel.target_ref === technique.id && + rel.relationship_type === 'mitigates' + ); + + if (relationship && relationship.description) { + console.log(` Guidance: ${relationship.description.substring(0, 100)}...\n`); + } + }); + } +``` + +## Step 7: Explore Transitive Relationships + +Add this method to trace complex relationship chains: + +```typescript + exploreTransitiveRelationships(): void { + console.log('🔄 TRANSITIVE RELATIONSHIPS (Group → Software → Techniques)\n'); + + // Find a group + const group = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0016' + ); + + if (!group) return; + + console.log(`👥 Group: ${group.name} (${group.external_references[0].external_id})`); + console.log(`📝 Description: ${group.description.substring(0, 120)}...\n`); + + // Get software used by the group + const software = group.getAssociatedSoftware(); + console.log(`💻 This group uses ${software.length} software tools:`); + + software.slice(0, 3).forEach((tool, index) => { + console.log(`\n${index + 1}. ${tool.name} (${tool.external_references[0].external_id})`); + + // Get techniques used by this software + const techniques = tool.getTechniques(); + console.log(` → Implements ${techniques.length} techniques:`); + + techniques.slice(0, 2).forEach((technique, techIndex) => { + console.log(` ${techIndex + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show tactics supported + const tactics = technique.getTactics(); + console.log(` Tactics: ${tactics.map(t => t.name).join(', ')}`); + }); + + if (techniques.length > 2) { + console.log(` ... and ${techniques.length - 2} more techniques`); + } + }); + + console.log(`\n💡 This shows the relationship chain: Group → Software → Techniques → Tactics\n`); + } +``` + +## Step 8: Run All Relationship Explorations + +Update your main function to run all examples: + +```typescript +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // Run all relationship explorations + explorer.exploreTechniqueTactics(); + explorer.exploreGroupTechniques(); + explorer.exploreSoftwareUsage(); + explorer.exploreSubtechniqueRelationships(); + explorer.exploreMitigationRelationships(); + explorer.exploreTransitiveRelationships(); + + console.log('🎉 Relationship exploration complete!\n'); + console.log('💡 Key takeaways:'); + console.log(' - ATT&CK objects are richly interconnected'); + console.log(' - Relationships carry descriptive context'); + console.log(' - Navigation methods simplify complex queries'); + console.log(' - Transitive relationships reveal attack patterns'); +} +``` + +## Step 9: Run Your Relationship Explorer + +Execute your script to see ATT&CK relationships in action: + +```bash +npx tsx relationship-explorer.ts +``` + +You'll see a comprehensive exploration of ATT&CK relationships, showing how different objects connect and relate to each other. + +## What You've Learned + +In this tutorial, you've mastered: + +1. **Technique-Tactic Mapping**: Understanding how techniques serve tactical goals +2. **Procedure Discovery**: Finding how groups use specific techniques +3. **Software Analysis**: Exploring tool capabilities and usage +4. **Hierarchy Navigation**: Working with parent and sub-technique relationships +5. **Mitigation Research**: Discovering defensive measures for techniques +6. **Transitive Relationships**: Tracing complex relationship chains + +## Key Relationship Navigation Methods + +You've used these essential methods: + +- **`getTactics()`**: Find tactics associated with a technique +- **`getTechniques()`**: Get techniques used by groups or software +- **`getSubtechniques()`** / **`getParentTechnique()`**: Navigate technique hierarchies +- **`getMitigations()`**: Find defensive measures for techniques +- **`getAssociatedSoftware()`**: Discover tools used by groups + +## Real-World Applications + +These relationship navigation skills enable: + +- **Threat Intelligence**: Mapping adversary capabilities and behaviors +- **Security Analysis**: Understanding attack patterns and defense priorities +- **Tool Development**: Building comprehensive security platforms +- **Research**: Discovering trends and patterns in adversary tactics + +## Advanced Relationship Patterns + +Try exploring these patterns on your own: + +```typescript +// Find all groups that use a specific technique +const groupsUsingTechnique = attackDataModel.groups.filter(group => + group.getTechniques().some(tech => tech.id === specificTechnique.id) +); + +// Find techniques that have no mitigations +const unmitigatedTechniques = attackDataModel.techniques.filter(tech => + tech.getMitigations().length === 0 +); + +// Find software used across multiple tactics +const multiTacticSoftware = attackDataModel.tools.filter(tool => + new Set(tool.getTechniques().flatMap(tech => tech.getTactics())).size > 3 +); +``` + +## Next Steps + +You've completed all tutorials! Now you can: + +- **[Apply these skills in How-to Guides](../how-to-guides/)** - Solve specific problems +- **[Explore the Reference Documentation](../reference/)** - Understand the complete API +- **[Read Explanations](../explanation/)** - Learn about design decisions and architecture +- **Build your own applications** using the patterns you've learned + +## Troubleshooting + +**"Cannot read property of undefined" errors**: Some objects might not have all expected relationships. Always check if methods return empty arrays or null values. + +**Performance with large datasets**: Relationship navigation is efficient, but filtering large result sets may take time. Consider caching results for repeated queries. + +**Missing relationships**: Remember that ATT&CK data evolves. Some relationships may not exist in all versions or domains. + +--- + +**Congratulations!** You've mastered ATT&CK relationship navigation and completed the tutorial series. You now have the skills to build sophisticated security analysis tools and conduct advanced threat intelligence research! diff --git a/docusaurus/docs/tutorials/technique-browser.md b/docusaurus/docs/tutorials/technique-browser.md new file mode 100644 index 00000000..06c40f08 --- /dev/null +++ b/docusaurus/docs/tutorials/technique-browser.md @@ -0,0 +1,448 @@ +# Building a Technique Browser + +**Create a complete application that browses ATT&CK techniques** + +In this tutorial, you'll build a complete command-line application that lets you browse and search ATT&CK techniques interactively. You'll learn to work with multiple object types, implement search functionality, and create a practical tool you can use and extend. + +## What You'll Build + +A command-line technique browser with these features: + +- Interactive menu system +- Search techniques by name or ATT&CK ID +- Browse techniques by tactic +- View technique details with relationships +- Navigate between parent techniques and sub-techniques + +## Prerequisites + +Complete the **[Your First ATT&CK Query](./your-first-query)** tutorial first to understand the basics. + +## Step 1: Set Up the Project + +Create a new project directory: + +```bash +mkdir attack-technique-browser +cd attack-technique-browser +npm init -y +``` + +Install dependencies including a library for interactive prompts: + +```bash +npm install @mitre-attack/attack-data-model prompts +npm install -D typescript tsx @types/node @types/prompts +``` + +## Step 2: Create the Core Browser Class + +Create `technique-browser.ts` with the foundation of our browser: + +```typescript +import { registerDataSource, loadDataModel, DataSource, AttackDataModel, TechniqueImpl } from '@mitre-attack/attack-data-model'; +import prompts from 'prompts'; + +class TechniqueBrowser { + private attackDataModel?: AttackDataModel; + private isInitialized = false; + + async initialize(): Promise { + console.log('🎯 Initializing ATT&CK Technique Browser...\n'); + + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + try { + const uuid = await registerDataSource(dataSource); + + if (uuid) { + this.attackDataModel = loadDataModel(uuid); + this.isInitialized = true; + + console.log(`✅ Loaded ${this.attackDataModel.techniques.length} techniques`); + console.log(`✅ Loaded ${this.attackDataModel.tactics.length} tactics\n`); + } + } catch (error) { + console.error('❌ Failed to initialize:', error); + throw error; + } + } + + private ensureInitialized(): AttackDataModel { + if (!this.isInitialized || !this.attackDataModel) { + throw new Error('Browser not initialized. Call initialize() first.'); + } + return this.attackDataModel; + } + + // We'll add more methods here... +} +``` + +## Step 3: Add the Main Menu + +Add the main menu system to the `TechniqueBrowser` class: + +```typescript + async run(): Promise { + this.ensureInitialized(); + + let running = true; + + while (running) { + console.log('\n' + '='.repeat(50)); + console.log('🎯 ATT&CK Technique Browser'); + console.log('='.repeat(50)); + + const response = await prompts({ + type: 'select', + name: 'action', + message: 'What would you like to do?', + choices: [ + { title: '🔍 Search techniques by name', value: 'search' }, + { title: '🏷️ Find technique by ATT&CK ID', value: 'findById' }, + { title: '🎯 Browse techniques by tactic', value: 'browseByTactic' }, + { title: '📊 Show statistics', value: 'stats' }, + { title: '👋 Exit', value: 'exit' } + ] + }); + + switch (response.action) { + case 'search': + await this.searchTechniques(); + break; + case 'findById': + await this.findTechniqueById(); + break; + case 'browseByTactic': + await this.browseByTactic(); + break; + case 'stats': + await this.showStatistics(); + break; + case 'exit': + running = false; + console.log('\n👋 Thanks for using ATT&CK Technique Browser!'); + break; + default: + console.log('Invalid selection. Please try again.'); + } + } + } +``` + +## Step 4: Implement Search Functionality + +Add these methods to implement search features: + +```typescript + private async searchTechniques(): Promise { + const attackDataModel = this.ensureInitialized(); + + const response = await prompts({ + type: 'text', + name: 'searchTerm', + message: 'Enter search term (technique name):', + validate: value => value.length < 1 ? 'Search term cannot be empty' : true + }); + + if (!response.searchTerm) return; + + const searchTerm = response.searchTerm.toLowerCase(); + const matches = attackDataModel.techniques.filter(technique => + technique.name.toLowerCase().includes(searchTerm) + ); + + if (matches.length === 0) { + console.log(`\n❌ No techniques found containing "${response.searchTerm}"`); + return; + } + + console.log(`\n🔍 Found ${matches.length} technique(s):`); + matches.slice(0, 10).forEach((technique, index) => { + const attackId = technique.external_references[0].external_id; + const isSubTech = technique.x_mitre_is_subtechnique ? ' (Sub-technique)' : ''; + console.log(`${index + 1}. ${technique.name} (${attackId})${isSubTech}`); + }); + + if (matches.length > 10) { + console.log(`... and ${matches.length - 10} more`); + } + + await this.selectAndViewTechnique(matches.slice(0, 10)); + } + + private async findTechniqueById(): Promise { + const attackDataModel = this.ensureInitialized(); + + const response = await prompts({ + type: 'text', + name: 'attackId', + message: 'Enter ATT&CK ID (e.g., T1003, T1055.001):', + validate: value => value.length < 1 ? 'ATT&CK ID cannot be empty' : true + }); + + if (!response.attackId) return; + + const technique = attackDataModel.techniques.find(tech => + tech.external_references[0].external_id.toLowerCase() === response.attackId.toLowerCase() + ); + + if (!technique) { + console.log(`\n❌ No technique found with ID "${response.attackId}"`); + return; + } + + await this.viewTechniqueDetails(technique); + } +``` + +## Step 5: Add Technique Viewing + +Add methods to display technique details: + +```typescript + private async selectAndViewTechnique(techniques: TechniqueImpl[]): Promise { + if (techniques.length === 0) return; + + const choices = techniques.map((technique, index) => ({ + title: `${technique.name} (${technique.external_references[0].external_id})`, + value: index + })); + + choices.push({ title: '← Back to main menu', value: -1 }); + + const response = await prompts({ + type: 'select', + name: 'selection', + message: 'Select a technique to view details:', + choices + }); + + if (response.selection >= 0) { + await this.viewTechniqueDetails(techniques[response.selection]); + } + } + + private async viewTechniqueDetails(technique: TechniqueImpl): Promise { + console.log('\n' + '='.repeat(60)); + console.log(`📋 ${technique.name}`); + console.log('='.repeat(60)); + + const attackId = technique.external_references[0].external_id; + console.log(`ATT&CK ID: ${attackId}`); + + if (technique.x_mitre_is_subtechnique) { + console.log('Type: Sub-technique'); + const parent = technique.getParentTechnique(); + if (parent) { + console.log(`Parent: ${parent.name} (${parent.external_references[0].external_id})`); + } + } else { + console.log('Type: Parent technique'); + const subtechniques = technique.getSubtechniques(); + if (subtechniques.length > 0) { + console.log(`Sub-techniques: ${subtechniques.length}`); + } + } + + console.log(`\nPlatforms: ${technique.x_mitre_platforms?.join(', ') || 'Not specified'}`); + + const tactics = technique.getTactics(); + console.log(`Tactics: ${tactics.map(t => t.name).join(', ')}`); + + console.log(`\nDescription:`); + console.log(technique.description); + + await prompts({ + type: 'confirm', + name: 'continue', + message: 'Press Enter to continue...', + initial: true + }); + } +``` + +## Step 6: Add Browse by Tactic Feature + +Add the ability to browse techniques organized by tactic: + +```typescript + private async browseByTactic(): Promise { + const attackDataModel = this.ensureInitialized(); + + const tactics = attackDataModel.tactics.sort((a, b) => a.name.localeCompare(b.name)); + + const choices = tactics.map(tactic => ({ + title: `${tactic.name} - ${tactic.description.substring(0, 60)}...`, + value: tactic + })); + + choices.push({ title: '← Back to main menu', value: null }); + + const response = await prompts({ + type: 'select', + name: 'tactic', + message: 'Select a tactic to browse techniques:', + choices + }); + + if (!response.tactic) return; + + const tacticTechniques = attackDataModel.techniques.filter(technique => + technique.getTactics().some(t => t.id === response.tactic.id) + ); + + console.log(`\n🎯 Techniques for "${response.tactic.name}" (${tacticTechniques.length} total):`); + + await this.selectAndViewTechnique(tacticTechniques); + } + + private async showStatistics(): Promise { + const attackDataModel = this.ensureInitialized(); + + const totalTechniques = attackDataModel.techniques.length; + const parentTechniques = attackDataModel.techniques.filter(t => !t.x_mitre_is_subtechnique).length; + const subTechniques = attackDataModel.techniques.filter(t => t.x_mitre_is_subtechnique).length; + + console.log('\n📊 ATT&CK Statistics:'); + console.log(`Total Techniques: ${totalTechniques}`); + console.log(`Parent Techniques: ${parentTechniques}`); + console.log(`Sub-techniques: ${subTechniques}`); + console.log(`Total Tactics: ${attackDataModel.tactics.length}`); + console.log(`Total Groups: ${attackDataModel.groups.length}`); + console.log(`Total Software: ${attackDataModel.malware.length + attackDataModel.tools.length}`); + + await prompts({ + type: 'confirm', + name: 'continue', + message: 'Press Enter to continue...', + initial: true + }); + } +``` + +## Step 7: Create the Main Application File + +Create `app.ts` to tie everything together: + +```typescript +import { TechniqueBrowser } from './technique-browser.js'; + +async function main() { + const browser = new TechniqueBrowser(); + + try { + await browser.initialize(); + await browser.run(); + } catch (error) { + console.error('❌ Application error:', error); + process.exit(1); + } +} + +main().catch(console.error); +``` + +## Step 8: Add TypeScript Configuration + +Create `tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist" + }, + "include": ["*.ts"], + "exclude": ["node_modules"] +} +``` + +## Step 9: Add Package Scripts + +Update your `package.json` to include helpful scripts: + +```json +{ + "scripts": { + "start": "npx tsx app.ts", + "dev": "npx tsx --watch app.ts", + "build": "npx tsc", + "run": "node dist/app.js" + }, + "type": "module" +} +``` + +## Step 10: Run Your Application + +Start your technique browser: + +```bash +npm start +``` + +You should see an interactive menu that lets you explore ATT&CK techniques in multiple ways! + +## Testing Your Browser + +Try these actions to explore your application: + +1. **Search for "credential"** - find techniques related to credential access +2. **Look up technique "T1003"** - view details about OS Credential Dumping +3. **Browse the "Initial Access" tactic** - see how adversaries get initial footholds +4. **Check statistics** - understand the scope of ATT&CK data + +## What You've Learned + +In this tutorial, you've learned how to: + +1. **Structure a complete application** using the ATT&CK Data Model +2. **Implement search functionality** across technique names and IDs +3. **Navigate relationships** between techniques, tactics, and sub-techniques +4. **Create interactive menus** for user-friendly data exploration +5. **Organize techniques by tactics** to understand adversary goals +6. **Display comprehensive technique details** including relationships + +## Key Concepts Mastered + +- **TechniqueImpl methods**: `getTactics()`, `getParentTechnique()`, `getSubtechniques()` +- **Relationship navigation**: Moving between related ATT&CK objects +- **Data filtering and searching**: Finding specific techniques in large datasets +- **Application architecture**: Organizing code into reusable classes +- **User interaction**: Creating friendly command-line interfaces + +## Extending Your Browser + +Consider adding these features: + +- Export search results to JSON or CSV +- Add filtering by platforms (Windows, Linux, macOS) +- Include software and groups that use techniques +- Add bookmarking for frequently viewed techniques +- Implement technique comparison features + +## Next Steps + +You're now ready for more advanced topics: + +- **[Understanding ATT&CK Relationships](./relationships)** - Master complex relationship navigation +- **[How-to Guides](../how-to-guides/)** - Solve specific problems like validation and schema extension +- **[Reference Documentation](../reference/)** - Explore the complete API + +--- + +**Excellent work!** You've built a complete ATT&CK technique browser and learned essential skills for working with ATT&CK data programmatically. diff --git a/docusaurus/docs/tutorials/your-first-query.md b/docusaurus/docs/tutorials/your-first-query.md new file mode 100644 index 00000000..7c6daa9b --- /dev/null +++ b/docusaurus/docs/tutorials/your-first-query.md @@ -0,0 +1,204 @@ +# Your First ATT&CK Query + +**Learn the basics of loading and exploring ATT&CK data** + +In this tutorial, you'll learn how to install the ATT&CK Data Model library, load your first ATT&CK dataset, and explore techniques and tactics. By the end, you'll have a working script that can query ATT&CK data and understand the basic concepts of the ATT&CK framework. + +## What You'll Build + +You'll create a simple Node.js script that: + +- Loads the latest ATT&CK Enterprise data +- Lists the first 5 techniques +- Shows the tactics associated with a technique +- Demonstrates basic relationship navigation + +## Step 1: Set Up Your Project + +First, create a new directory for your project and initialize it: + +```bash +mkdir my-first-attack-query +cd my-first-attack-query +npm init -y +``` + +You should see output like: + +```shell +Wrote to /path/to/my-first-attack-query/package.json +``` + +## Step 2: Install the ATT&CK Data Model + +Install the ATT&CK Data Model library and TypeScript tools: + +```bash +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +You should see the packages being installed. The installation may take a minute as it downloads all dependencies. + +## Step 3: Create Your First Script + +Create a file named `first-query.ts` and add the following code: + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +async function exploreAttackData() { + console.log('🎯 Loading ATT&CK Enterprise data...\n'); + + // Step 1: Create a data source + const dataSource = new DataSource({ + source: 'attack', // Load from official ATT&CK repository + domain: 'enterprise-attack', // Focus on Enterprise domain + version: '15.1', // Use specific version for consistency + parsingMode: 'relaxed' // Continue even if some data has minor issues + }); + + try { + // Step 2: Register the data source + const uuid = await registerDataSource(dataSource); + + if (uuid) { + console.log('✅ Data source registered successfully!\n'); + + // Step 3: Load the data model + const attackDataModel = loadDataModel(uuid); + console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); + + // Step 4: Explore the first few techniques + console.log('🔍 First 5 techniques:'); + attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + }); + + } else { + console.error('❌ Failed to register data source'); + } + + } catch (error) { + console.error('❌ Error:', error); + } +} + +// Run the function +exploreAttackData(); +``` + +## Step 4: Run Your Script + +Execute your script to see the ATT&CK data in action: + +```bash +npx tsx first-query.ts +``` + +You should see output similar to: + +```shell +🎯 Loading ATT&CK Enterprise data... + +✅ Data source registered successfully! + +📊 Loaded 196+ techniques + +🔍 First 5 techniques: +1. OS Credential Dumping (T1003) +2. Boot or Logon Autostart Execution (T1547) +3. Process Injection (T1055) +4. Command and Scripting Interpreter (T1059) +5. Ingress Tool Transfer (T1105) +``` + +**Congratulations!** You've just loaded and explored your first ATT&CK dataset! + +## Step 5: Explore Technique Details + +Now let's look at more details about a specific technique. Add this code to your script after the previous console.log statements: + +```typescript + console.log('\n🔎 Examining a specific technique:'); + const firstTechnique = attackDataModel.techniques[0]; + + console.log(`Name: ${firstTechnique.name}`); + console.log(`ID: ${firstTechnique.external_references[0].external_id}`); + console.log(`Description: ${firstTechnique.description.substring(0, 100)}...`); + + // Check if it's a subtechnique + if (firstTechnique.x_mitre_is_subtechnique) { + console.log('📌 This is a sub-technique'); + } else { + console.log('📌 This is a parent technique'); + } +``` + +## Step 6: Discover Associated Tactics + +ATT&CK techniques are organized under tactics (the "why" behind adversary actions). Let's explore this relationship: + +```typescript + console.log('\n🎯 Associated tactics:'); + const tactics = firstTechnique.getTactics(); + + tactics.forEach((tactic) => { + console.log(`- ${tactic.name}: ${tactic.description.substring(0, 60)}...`); + }); +``` + +## Step 7: Run Your Enhanced Script + +Run your enhanced script to see the additional information: + +```bash +npx tsx first-query.ts +``` + +Your output should now include technique details and associated tactics, showing you the rich relationships within ATT&CK data. + +## What You've Learned + +In this tutorial, you've learned: + +1. **How to install** the ATT&CK Data Model library +2. **How to create a DataSource** pointing to official ATT&CK data +3. **How to register and load** ATT&CK datasets +4. **How to access technique** properties like name, ID, and description +5. **How to navigate relationships** between techniques and tactics +6. **The difference** between parent techniques and sub-techniques + +## Understanding the Code + +Let's break down the key concepts you used: + +- **DataSource**: Tells the library where to find ATT&CK data (official repository, local file, URL, etc.) +- **registerDataSource**: Validates and caches the data source, returning a unique identifier +- **loadDataModel**: Retrieves the cached AttackDataModel instance using the identifier +- **AttackDataModel**: The main class containing all ATT&CK objects with automatic relationship mapping +- **getTactics()**: A relationship navigation method that returns related tactics + +## Next Steps + +Now that you understand the basics, you're ready for the next tutorial: + +- **[Building a Technique Browser](./technique-browser)** - Create a complete application that browses techniques + +Or explore other documentation: + +- **[How-to Guides](../how-to-guides/)** - Solve specific problems +- **[Reference](../reference/)** - Detailed API documentation +- **[Examples](https://github.com/mitre-attack/attack-data-model/tree/main/examples)** - More code samples + +## Common Issues + +**"Module not found" errors**: Make sure you've installed all dependencies with `npm install` + +**Network errors during data loading**: The library downloads ATT&CK data from GitHub. Ensure you have internet connectivity. + +**TypeScript compilation errors**: Make sure you're using `npx tsx` to run TypeScript directly, or compile with `npx tsc` first. + +--- + +**Great job!** You've completed your first ATT&CK query and learned the fundamental concepts. Ready for the next challenge? diff --git a/docusaurus/docusaurus.config.ts b/docusaurus/docusaurus.config.ts index 9461f7ed..9af672fb 100644 --- a/docusaurus/docusaurus.config.ts +++ b/docusaurus/docusaurus.config.ts @@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic'; const config: Config = { title: 'MITRE ATT&CK Data Model', - tagline: '', + tagline: 'A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1', favicon: 'img/favicon.ico', // Set the production url of your site here @@ -35,6 +35,9 @@ const config: Config = { { docs: { sidebarPath: './sidebars.ts', + editUrl: 'https://github.com/mitre-attack/attack-data-model/tree/main/docusaurus/', + showLastUpdateAuthor: true, + showLastUpdateTime: true, }, // blog: { // showReadingTime: false, @@ -50,6 +53,17 @@ const config: Config = { ], themeConfig: { + // Enhanced metadata for better SEO and social sharing + metadata: [ + { + name: 'keywords', + content: 'MITRE, ATT&CK, STIX, TypeScript, threat intelligence, cybersecurity, data model', + }, + { + name: 'description', + content: 'A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1. Provides type-safe access to ATT&CK objects.', + }, + ], navbar: { title: 'ATT&CK Data Model', logo: { @@ -59,9 +73,9 @@ const config: Config = { items: [ { type: 'docSidebar', - sidebarId: 'tutorialSidebar', + sidebarId: 'documentationSidebar', position: 'left', - label: 'ATT&CK Schemas', + label: 'Documentation', }, // { // to: '/known-issues', @@ -75,23 +89,70 @@ const config: Config = { }, ], }, + // Enable local search for better user experience + // Note: For production, consider configuring Algolia search + docs: { + sidebar: { + hideable: true, + autoCollapseCategories: false, + }, + }, footer: { links: [ { - label: 'ATT&CK Website', - href: 'https://attack.mitre.org', - }, - { - label: 'Contact Us', - href: 'https://attack.mitre.org/resources/engage-with-attack/contact', + title: 'Documentation', + items: [ + { + label: 'Tutorials', + to: '/docs/tutorials/', + }, + { + label: 'How-to Guides', + to: '/docs/how-to-guides/', + }, + { + label: 'Reference', + to: '/docs/reference/', + }, + { + label: 'Explanation', + to: '/docs/explanation/', + }, + ], }, { - label: 'Terms of Use', - href: 'https://attack.mitre.org/resources/legal-and-branding/terms-of-use', + title: 'Community', + items: [ + { + label: 'GitHub', + href: 'https://github.com/mitre-attack/attack-data-model', + }, + { + label: 'Issues', + href: 'https://github.com/mitre-attack/attack-data-model/issues', + }, + ], }, { - label: 'Privacy Policy', - href: 'https://attack.mitre.org/resources/legal-and-branding/privacy', + title: 'MITRE ATT&CK', + items: [ + { + label: 'ATT&CK Website', + href: 'https://attack.mitre.org', + }, + { + label: 'Contact Us', + href: 'https://attack.mitre.org/resources/engage-with-attack/contact', + }, + { + label: 'Terms of Use', + href: 'https://attack.mitre.org/resources/legal-and-branding/terms-of-use', + }, + { + label: 'Privacy Policy', + href: 'https://attack.mitre.org/resources/legal-and-branding/privacy', + }, + ], }, ], style: 'dark', diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index 4e14e296..ca749904 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -1,39 +1,72 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [ + documentationSidebar: [ { type: 'doc', - id: 'overview', + id: 'index', + label: 'Documentation Home' }, { type: 'category', - label: 'STIX Domain Objects', + label: 'Tutorials', + description: 'Learning-oriented guides for getting started', items: [ { type: 'autogenerated', - dirName: 'sdo', + dirName: 'tutorials', }, ], }, { type: 'category', - label: 'STIX Relationship Objects', + label: 'How-to Guides', + description: 'Problem-oriented solutions for specific tasks', items: [ { type: 'autogenerated', - dirName: 'sro', + dirName: 'how-to-guides', }, ], }, { type: 'category', - label: 'STIX Meta Objects', + label: 'Reference', + description: 'Information-oriented technical specifications', + items: [ + 'reference/index', + { + type: 'category', + label: 'API Documentation', + items: [ + { + type: 'autogenerated', + dirName: 'reference/api', + }, + ], + }, + { + type: 'category', + label: 'Schema Reference', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas', + }, + ], + }, + 'reference/errors', + 'reference/configuration', + ], + }, + { + type: 'category', + label: 'Explanation', + description: 'Understanding-oriented context and design rationale', items: [ { type: 'autogenerated', - dirName: 'smo', + dirName: 'explanation', }, ], }, diff --git a/docusaurus/src/components/DocTypeIndicator/index.tsx b/docusaurus/src/components/DocTypeIndicator/index.tsx new file mode 100644 index 00000000..77406b24 --- /dev/null +++ b/docusaurus/src/components/DocTypeIndicator/index.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './styles.module.css'; + +export type DocType = 'tutorial' | 'how-to' | 'reference' | 'explanation'; + +export interface DocTypeIndicatorProps { + type: DocType; + size?: 'small' | 'medium' | 'large'; + showLabel?: boolean; + className?: string; +} + +const docTypeConfig = { + tutorial: { + emoji: '🎓', + label: 'Tutorial', + description: 'Learning-oriented', + color: '#10b981', // emerald-500 + bgColor: '#d1fae5', // emerald-100 + }, + 'how-to': { + emoji: '🔧', + label: 'How-to Guide', + description: 'Problem-oriented', + color: '#f59e0b', // amber-500 + bgColor: '#fef3c7', // amber-100 + }, + reference: { + emoji: '📖', + label: 'Reference', + description: 'Information-oriented', + color: '#3b82f6', // blue-500 + bgColor: '#dbeafe', // blue-100 + }, + explanation: { + emoji: '💡', + label: 'Explanation', + description: 'Understanding-oriented', + color: '#8b5cf6', // violet-500 + bgColor: '#ede9fe', // violet-100 + }, +}; + +export default function DocTypeIndicator({ + type, + size = 'medium', + showLabel = true, + className, +}: DocTypeIndicatorProps): JSX.Element { + const config = docTypeConfig[type]; + + return ( +
+ + {config.emoji} + + {showLabel && ( +
+ {config.label} + {config.description} +
+ )} +
+ ); +} + +export function DocTypeBadge({ + type, + className, +}: { + type: DocType; + className?: string; +}): JSX.Element { + return ( + + ); +} + +export function DocTypeCard({ + type, + title, + description, + href, + className, +}: { + type: DocType; + title: string; + description: string; + href: string; + className?: string; +}): JSX.Element { + const config = docTypeConfig[type]; + + return ( + +
+ +
+

{title}

+ {config.label} +
+
+

{description}

+
+ {config.description} + +
+
+ ); +} diff --git a/docusaurus/src/components/DocTypeIndicator/styles.module.css b/docusaurus/src/components/DocTypeIndicator/styles.module.css new file mode 100644 index 00000000..356d3f11 --- /dev/null +++ b/docusaurus/src/components/DocTypeIndicator/styles.module.css @@ -0,0 +1,214 @@ +/* Base doc type indicator styles */ +.docTypeIndicator { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background-color: var(--doc-type-bg-color); + border-radius: 0.5rem; + border: 1px solid var(--doc-type-color); + font-weight: 500; +} + +/* Size variations */ +.size-small { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + gap: 0.25rem; +} + +.size-small .emoji { + font-size: 1rem; +} + +.size-medium { + padding: 0.5rem 0.75rem; + font-size: 1rem; + gap: 0.5rem; +} + +.size-medium .emoji { + font-size: 1.25rem; +} + +.size-large { + padding: 0.75rem 1rem; + font-size: 1.125rem; + gap: 0.75rem; +} + +.size-large .emoji { + font-size: 1.5rem; +} + +/* Emoji styles */ +.emoji { + flex-shrink: 0; + line-height: 1; +} + +/* Label container */ +.labelContainer { + display: flex; + flex-direction: column; + gap: 0.125rem; + min-width: 0; +} + +.label { + color: var(--doc-type-color); + font-weight: 600; + line-height: 1.2; +} + +.description { + font-size: 0.75em; + color: var(--ifm-color-emphasis-600); + line-height: 1; + font-weight: 400; +} + +/* Badge variant */ +.badge { + padding: 0.25rem 0.5rem; + border-radius: 9999px; + font-size: 0.875rem; + font-weight: 600; +} + +.badge .emoji { + font-size: 1rem; +} + +/* Doc type card styles */ +.docTypeCard { + display: block; + padding: 1.5rem; + background: var(--ifm-card-background-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0.75rem; + text-decoration: none; + color: inherit; + transition: all 0.2s ease; + position: relative; + overflow: hidden; +} + +.docTypeCard::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: var(--doc-type-color); +} + +.docTypeCard:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px -8px var(--doc-type-color); + text-decoration: none; + color: inherit; +} + +.cardHeader { + display: flex; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1rem; +} + +.cardHeaderText { + flex: 1; + min-width: 0; +} + +.cardTitle { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--ifm-heading-color); + line-height: 1.3; +} + +.cardTypeLabel { + display: inline-block; + margin-top: 0.25rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--doc-type-bg-color); + color: var(--doc-type-color); + border-radius: 9999px; + border: 1px solid var(--doc-type-color); +} + +.cardDescription { + margin: 0 0 1rem 0; + color: var(--ifm-color-emphasis-700); + line-height: 1.5; +} + +.cardFooter { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.875rem; +} + +.cardOrientation { + color: var(--ifm-color-emphasis-600); + font-style: italic; +} + +.cardArrow { + color: var(--doc-type-color); + font-weight: 600; + font-size: 1.125rem; + transition: transform 0.2s ease; +} + +.docTypeCard:hover .cardArrow { + transform: translateX(4px); +} + +/* Dark theme adjustments */ +[data-theme='dark'] .docTypeCard { + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .docTypeCard:hover { + box-shadow: 0 8px 25px -8px rgba(0, 0, 0, 0.3); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .cardHeader { + gap: 0.75rem; + } + + .cardTitle { + font-size: 1.125rem; + } + + .docTypeCard { + padding: 1.25rem; + } +} + +/* Focus styles for accessibility */ +.docTypeCard:focus { + outline: 2px solid var(--doc-type-color); + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .docTypeIndicator { + border-width: 2px; + } + + .cardTypeLabel { + border-width: 2px; + } +} diff --git a/docusaurus/src/components/HomepageFeatures/index.tsx b/docusaurus/src/components/HomepageFeatures/index.tsx index 2c2ac320..7a9843da 100644 --- a/docusaurus/src/components/HomepageFeatures/index.tsx +++ b/docusaurus/src/components/HomepageFeatures/index.tsx @@ -1,61 +1,60 @@ import clsx from 'clsx'; import Heading from '@theme/Heading'; +import { DocTypeCard } from '../DocTypeIndicator'; import styles from './styles.module.css'; -type FeatureItem = { - title: string; - description: JSX.Element; -}; +const documentationTypes = [ + { + type: 'tutorial' as const, + title: 'Tutorials', + description: 'Step-by-step guides to get you started with ATT&CK data processing. Perfect for beginners who want hands-on experience.', + href: 'docs/tutorials/', + }, + { + type: 'how-to' as const, + title: 'How-to Guides', + description: 'Practical solutions for experienced users who know what they want to achieve and need direct guidance.', + href: 'docs/how-to-guides/', + }, + { + type: 'reference' as const, + title: 'Reference', + description: 'Complete API documentation, configuration options, and technical specifications for when you need precise information.', + href: 'docs/reference/', + }, + { + type: 'explanation' as const, + title: 'Explanation', + description: 'Deep dives into the architecture, design philosophy, and trade-offs that shape the library.', + href: 'docs/explanation/', + }, +]; -const FeatureList: FeatureItem[] = [ +const keyFeatures = [ { - title: 'Zod Schema Definitions', - description: ( - <> - Zod is a powerful TypeScript-first schema declaration and validation library that we use to - define our ATT&CK Data Model. By utilizing Zod, we ensure that our data structures are - robust, consistent, and easy to maintain. The schemas allow us to generate documentation, - providing clear and concise information for developers and ATT&CK users. This approach not - only streamlines our development process but also enhances the reliability of our data - model. With Zod, we can easily adapt to changes and ensure that our data model remains - accurate and up-to-date. - - ), + title: '🔒 Type-Safe', + description: 'Full TypeScript support with compile-time validation and IntelliSense.', }, { - title: 'Benefits and Uses', - description: ( - <> - This site leverages Zod schemas to automatically generate comprehensive documentation, - ensuring it is always in sync with the underlying data model. By minimizing documentation - discrepancies previously prone to human error, this approach ensures our documentation - remains accessible and user-friendly. Beyond documentation, the ATT&CK Data Model allows - users to parse, validate, and utilize the data in the ATT&CK knowledge base as a TypeScript - object. Integrating Zod ensures data handling is efficient and reliable, enhancing the - accuracy and usability of our data to support developers in their work. - - ), + title: '✅ STIX Compliant', + description: 'Built on STIX 2.1 standards for seamless threat intelligence integration.', }, { - title: 'Known Compliance Issues', - description: ( - <> - Currently, the ATT&CK knowledge base does not fully conform to the defined Zod schemas, and - there are known discrepancies that need to be addressed. We are actively working on aligning - our data with the schemas to ensure complete compliance. Your understanding and patience are - appreciated as we work to make improvements. - - ), + title: '🔗 Rich Relationships', + description: 'Intuitive navigation between techniques, tactics, groups, and more.', + }, + { + title: '📊 Multi-Domain', + description: 'Supports Enterprise, Mobile, and ICS ATT&CK domains.', }, ]; -function Feature({ title, description }: FeatureItem) { +function KeyFeature({ title, description }: { title: string; description: string }) { return ( -
-
-
- {title} -

{description}

+
+
+

{title}

+

{description}

); @@ -63,14 +62,91 @@ function Feature({ title, description }: FeatureItem) { export default function HomepageFeatures(): JSX.Element { return ( -
-
-
- {FeatureList.map((props, idx) => ( - - ))} + <> + {/* Documentation Navigation Section */} +
+
+
+ + Choose Your Documentation Path + +

+ Our documentation aims to provide exactly the right information for your needs. + Let us know if anything is missing! +

+
+
+ {documentationTypes.map((docType, idx) => ( +
+ +
+ ))} +
-
-
+ + + {/* Key Features Section */} +
+
+
+ + Library Features + +

+ Built for type safety, standards compliance, and developer experience +

+
+
+ {keyFeatures.map((feature, idx) => ( + + ))} +
+
+
+ + {/* Quick Start Section */} +
+
+
+ + Quick Start + +

+ Get up and running with ATT&CK data in minutes +

+
+
+
+
+
+

Install

+
npm install @mitre-attack/attack-data-model
+
+
+
+
+

Import and Use

+
{`import { registerDataSource, loadDataModel } from '@mitre-attack/attack-data-model';
+
+const uuid = await registerDataSource(dataSource);
+const attackModel = loadDataModel(uuid);`}
+
+
+
+ +
+
+
+ ); } diff --git a/docusaurus/src/components/HomepageFeatures/styles.module.css b/docusaurus/src/components/HomepageFeatures/styles.module.css index b248eb2e..91b464c7 100644 --- a/docusaurus/src/components/HomepageFeatures/styles.module.css +++ b/docusaurus/src/components/HomepageFeatures/styles.module.css @@ -1,11 +1,224 @@ -.features { - display: flex; +/* Section styles */ +.documentationSection { + padding: 4rem 0; + background: var(--ifm-background-surface-color); +} + +.featuresSection { + padding: 4rem 0; + background: var(--ifm-color-emphasis-100); +} + +.quickStartSection { + padding: 4rem 0; + background: var(--ifm-background-surface-color); +} + +/* Common section elements */ +.sectionTitle { + margin-bottom: 1rem; + font-size: 2.5rem; + font-weight: 700; + color: var(--ifm-heading-color); +} + +.sectionSubtitle { + font-size: 1.125rem; + color: var(--ifm-color-emphasis-700); + max-width: 600px; + margin: 0 auto; + line-height: 1.6; +} + +/* Documentation cards grid */ +.docTypeGrid { + gap: 2rem; + margin-top: 2rem; +} + +.docTypeCard { + height: 100%; + margin-bottom: 2rem; +} + +/* Key features */ +.keyFeature { + margin-bottom: 2rem; +} + +.keyFeatureContent { + text-align: center; + padding: 1.5rem; + height: 100%; + border-radius: 0.75rem; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.keyFeatureContent:hover { + transform: translateY(-4px); + box-shadow: 0 8px 25px -8px var(--ifm-color-emphasis-400); +} + +.keyFeatureTitle { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--ifm-heading-color); +} + +.keyFeatureDescription { + color: var(--ifm-color-emphasis-700); + line-height: 1.5; + margin: 0; +} + +/* Quick start section */ +.quickStartContent { + max-width: 900px; + margin: 0 auto; +} + +.codeBlock { + background: var(--ifm-code-background); + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 0.5rem; + padding: 1.5rem; + height: 100%; +} + +.codeBlock h4 { + margin: 0 0 1rem 0; + color: var(--ifm-heading-color); + font-weight: 600; +} + +.codeBlock pre { + margin: 0; + padding: 0; + background: transparent; + border: none; + font-size: 0.875rem; + line-height: 1.5; +} + +.codeBlock code { + background: transparent; + padding: 0; + border-radius: 0; + color: var(--ifm-color-emphasis-800); + font-family: var(--ifm-font-family-monospace); +} + +.primaryButton { + display: inline-flex; align-items: center; - padding: 2rem 0; - width: 100%; + padding: 1rem 2rem; + background: var(--ifm-color-primary); + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 600; + font-size: 1.125rem; + transition: all 0.2s ease; +} + +.primaryButton:hover { + background: var(--ifm-color-primary-dark); + color: white; + text-decoration: none; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.primaryButton:active { + transform: translateY(0); +} + +/* Dark theme adjustments */ +[data-theme='dark'] .featuresSection { + background: var(--ifm-color-emphasis-200); +} + +[data-theme='dark'] .keyFeatureContent { + background: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .keyFeatureContent:hover { + box-shadow: 0 8px 25px -8px rgba(0, 0, 0, 0.4); +} + +[data-theme='dark'] .codeBlock { + background: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-400); } -.featureSvg { - height: 200px; - width: 200px; +/* Responsive design */ +@media (max-width: 996px) { + .sectionTitle { + font-size: 2rem; + } + + .documentationSection, + .featuresSection, + .quickStartSection { + padding: 3rem 0; + } + + .docTypeGrid { + gap: 1.5rem; + } + + .keyFeatureContent { + padding: 1.25rem; + } +} + +@media (max-width: 768px) { + .sectionTitle { + font-size: 1.75rem; + } + + .sectionSubtitle { + font-size: 1rem; + } + + .documentationSection, + .featuresSection, + .quickStartSection { + padding: 2rem 0; + } + + .codeBlock { + padding: 1.25rem; + } + + .codeBlock h4 { + font-size: 1rem; + } + + .primaryButton { + padding: 0.875rem 1.5rem; + font-size: 1rem; + } +} + +/* Focus styles for accessibility */ +.primaryButton:focus, +.keyFeatureContent:focus { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .keyFeatureContent { + border-width: 2px; + } + + .codeBlock { + border-width: 2px; + } } diff --git a/docusaurus/src/css/custom.css b/docusaurus/src/css/custom.css index 51135768..b2be69fc 100644 --- a/docusaurus/src/css/custom.css +++ b/docusaurus/src/css/custom.css @@ -5,6 +5,7 @@ */ :root { + /* Primary brand colors */ --ifm-color-primary: #c64227; --ifm-color-primary-dark: #b64227; --ifm-color-primary-darker: #a64227; @@ -12,11 +13,50 @@ --ifm-color-primary-light: #d64227; --ifm-color-primary-lighter: #e64227; --ifm-color-primary-lightest: #f64227; + + /* Documentation type colors */ + --doc-tutorial-color: #10b981; + --doc-tutorial-bg: #d1fae5; + --doc-tutorial-border: #6ee7b7; + + --doc-howto-color: #f59e0b; + --doc-howto-bg: #fef3c7; + --doc-howto-border: #fcd34d; + + --doc-reference-color: #3b82f6; + --doc-reference-bg: #dbeafe; + --doc-reference-border: #93c5fd; + + --doc-explanation-color: #8b5cf6; + --doc-explanation-bg: #ede9fe; + --doc-explanation-border: #c4b5fd; + + /* Enhanced typography */ + --ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', 'Roboto', 'Ubuntu', 'Cantarell', 'Noto Sans', sans-serif; + --ifm-font-family-monospace: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace; + + /* Code styling */ --ifm-code-font-size: 95%; + --ifm-code-padding-horizontal: 0.375rem; + --ifm-code-padding-vertical: 0.125rem; + --ifm-code-border-radius: 0.375rem; + + /* Enhanced spacing */ + --ifm-spacing-horizontal: 1.5rem; + --ifm-global-spacing: 1.5rem; + + /* Improved line heights */ + --ifm-line-height-base: 1.6; + --ifm-heading-line-height: 1.3; + + /* Code block styling */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --ifm-pre-padding: 1.5rem; + --ifm-pre-border-radius: 0.5rem; } [data-theme='dark'] { + /* Primary brand colors for dark theme */ --ifm-color-primary: #c64227; --ifm-color-primary-dark: #b64227; --ifm-color-primary-darker: #a64227; @@ -24,5 +64,253 @@ --ifm-color-primary-light: #d64227; --ifm-color-primary-lighter: #e64227; --ifm-color-primary-lightest: #f64227; + + /* Documentation type colors for dark theme */ + --doc-tutorial-color: #34d399; + --doc-tutorial-bg: #064e3b; + --doc-tutorial-border: #047857; + + --doc-howto-color: #fbbf24; + --doc-howto-bg: #451a03; + --doc-howto-border: #92400e; + + --doc-reference-color: #60a5fa; + --doc-reference-bg: #1e3a8a; + --doc-reference-border: #1d4ed8; + + --doc-explanation-color: #a78bfa; + --doc-explanation-bg: #4c1d95; + --doc-explanation-border: #6d28d9; + + /* Code block styling for dark theme */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +} + +/* Enhanced typography */ +body { + font-family: var(--ifm-font-family-base); + line-height: var(--ifm-line-height-base); + font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1; +} + +h1, h2, h3, h4, h5, h6 { + line-height: var(--ifm-heading-line-height); + font-weight: 600; + letter-spacing: -0.025em; +} + +/* Improved code styling */ +code { + font-family: var(--ifm-font-family-monospace); + font-weight: 500; + font-feature-settings: 'calt' 1; +} + +pre code { + font-weight: 400; +} + +/* Enhanced admonitions */ +.admonition { + border-radius: 0.5rem; + border-left-width: 4px; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +/* Documentation type-specific styling */ +.doc-tutorial { + border-left-color: var(--doc-tutorial-color); + background: var(--doc-tutorial-bg); +} + +.doc-howto { + border-left-color: var(--doc-howto-color); + background: var(--doc-howto-bg); +} + +.doc-reference { + border-left-color: var(--doc-reference-color); + background: var(--doc-reference-bg); +} + +.doc-explanation { + border-left-color: var(--doc-explanation-color); + background: var(--doc-explanation-bg); +} + +/* Enhanced table styling */ +table { + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +th { + font-weight: 600; + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.05em; +} + +/* Improved blockquote styling */ +blockquote { + border-left-color: var(--ifm-color-primary); + border-left-width: 4px; + border-radius: 0 0.375rem 0.375rem 0; + background: var(--ifm-color-emphasis-100); + margin: 1.5rem 0; + padding: 1rem 1.5rem; + font-style: normal; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +/* Enhanced button styling */ +.button { + font-weight: 500; + border-radius: 0.375rem; + transition: all 0.2s ease; +} + +.button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.button--primary { + background: var(--ifm-color-primary); +} + +.button--primary:hover { + background: var(--ifm-color-primary-dark); +} + +/* Enhanced card styling */ +.card { + border-radius: 0.75rem; + border: 1px solid var(--ifm-color-emphasis-200); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + transition: all 0.2s ease; +} + +.card:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + transform: translateY(-2px); +} + +/* Improved navbar styling */ +.navbar { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + backdrop-filter: blur(8px); +} + +/* Enhanced sidebar styling */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--ifm-color-emphasis-200); +} + +.theme-doc-sidebar-item-category { + margin-bottom: 0.5rem; +} + +.theme-doc-sidebar-item-link { + border-radius: 0.375rem; + transition: all 0.15s ease; +} + +.theme-doc-sidebar-item-link:hover { + background: var(--ifm-color-emphasis-100); +} + +.theme-doc-sidebar-item-link-level-1.theme-doc-sidebar-item-link--active { + background: var(--ifm-color-primary); + color: white; +} + +/* Improved code block styling */ +.prism-code { + border-radius: var(--ifm-pre-border-radius); + font-family: var(--ifm-font-family-monospace); + font-weight: 400; + font-size: 0.875rem; + line-height: 1.5; +} + +/* Enhanced search styling */ +.DocSearch-Button { + border-radius: 0.5rem; + transition: all 0.2s ease; +} + +.DocSearch-Button:hover { + background: var(--ifm-color-emphasis-200); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Utility classes for documentation types */ +.tutorial-accent { + color: var(--doc-tutorial-color); +} + +.howto-accent { + color: var(--doc-howto-color); +} + +.reference-accent { + color: var(--doc-reference-color); +} + +.explanation-accent { + color: var(--doc-explanation-color); +} + +/* Responsive improvements */ +@media (max-width: 768px) { + :root { + --ifm-spacing-horizontal: 1rem; + --ifm-global-spacing: 1rem; + --ifm-pre-padding: 1rem; + } + + blockquote { + margin: 1rem 0; + padding: 0.75rem 1rem; + } + + .admonition { + margin: 1rem 0; + } +} + +/* Accessibility improvements */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .card, + .admonition, + table { + border-width: 2px; + } + + .button { + border-width: 2px; + } +} + +/* Focus improvements for accessibility */ +:focus-visible { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; } \ No newline at end of file diff --git a/generate-docs.sh b/generate-docs.sh index 38485da8..896ca2fe 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -2,13 +2,13 @@ WORKDIR="." SCHEMA_DIR="$WORKDIR/src/schemas" -OUTPUT_DIR="docusaurus/docs" -OVERVIEW="$OUTPUT_DIR/overview.md" +OUTPUT_DIR="docusaurus/docs/reference/schemas" +OVERVIEW="$OUTPUT_DIR/index.md" mkdir -p $OUTPUT_DIR -# init overview.md -echo "# ATT&CK Schemas" > $OVERVIEW +# init index.md +echo "# Schema Reference" > $OVERVIEW echo "" >> $OVERVIEW # attack spec version @@ -30,7 +30,7 @@ find $SCHEMA_DIR -name "*.schema.ts" | while read schemaFile; do # skip stix-bundle (manually generated) and add to overview page if [[ "${fileName}" == "stix-bundle.schema.ts" ]]; then - echo "| STIX Bundle | SDO | [Schema](/docs/sdo/stix-bundle.schema) |" >> $OVERVIEW + echo "| STIX Bundle | SDO | [Schema](./sdo/stix-bundle.schema) |" >> $OVERVIEW continue fi @@ -52,6 +52,6 @@ find $SCHEMA_DIR -name "*.schema.ts" | while read schemaFile; do schemaLink="${relativePath/.ts/.md}" stixType=$(dirname "$relativePath" | cut -d '/' -f 1 | tr '[:lower:]' '[:upper:]') - echo "| $title | $stixType | [Schema](/docs/$schemaLink) |" >> $OVERVIEW + echo "| $title | $stixType | [Schema](./$schemaLink) |" >> $OVERVIEW -done \ No newline at end of file +done From 5c7e98f10fd9c6c1f5d987d7ed7f8b9ab6283b9d Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 8 Aug 2025 13:44:05 -0500 Subject: [PATCH 03/39] docs: update broken link --- docusaurus/docs/reference/api/attack-data-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/reference/api/attack-data-model.md b/docusaurus/docs/reference/api/attack-data-model.md index 80f13043..f6c09c97 100644 --- a/docusaurus/docs/reference/api/attack-data-model.md +++ b/docusaurus/docs/reference/api/attack-data-model.md @@ -302,5 +302,5 @@ const highValueTargets = attackDataModel.techniques.filter(t => { ## See Also - **[DataSource](./data-sources)** - Data source configuration and loading -- **[Implementation Classes](../schemas/sdo/)** - Individual object class documentation +- **[Implementation Classes](../schemas/)** - Individual object class documentation - **[Relationships](../schemas/sro/relationship.schema)** - Relationship type specifications From 2a37f23cd7e927d0cd709b0dfacd8bbc577c7ee8 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Sun, 10 Aug 2025 23:54:43 -0500 Subject: [PATCH 04/39] docs: remove erroneous registered trademark symbols --- .gitignore | 2 +- README.md | 8 ++++---- .../docs/explanation/attack-specification-overview.md | 2 +- docusaurus/docs/index.md | 2 +- docusaurus/docs/overview.md | 4 ++-- docusaurus/docs/reference/api/index.md | 2 +- docusaurus/docs/tutorials/index.md | 2 +- docusaurus/src/pages/index.tsx | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 183ec516..87c25047 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# auto-generated schema documentation +# auto-generated schema documentation docusaurus/docs/reference/schemas/ # TypeScript artifacts diff --git a/README.md b/README.md index 497f65f3..10ab13f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MITRE ATT&CK® Data Model -**A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1** +**A TypeScript library for working with MITRE ATT&CK data using STIX 2.1** The ATT&CK Data Model (ADM) provides a type-safe, object-oriented interface for working with MITRE ATT&CK datasets. Built on STIX 2.1 compliance, it uses Zod schemas and TypeScript types to ensure data integrity while providing intuitive relationship navigation between ATT&CK objects. @@ -150,7 +150,7 @@ For more detailed examples, please refer to the [examples](./examples/README.md) ## Compatibility Matrix -Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/explanation/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK® dataset (`mitre-attack/attack-stix-data`). +Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/explanation/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK dataset (`mitre-attack/attack-stix-data`). ## Contributing @@ -164,6 +164,6 @@ This project is licensed under the Apache 2.0 License. Copyright 2020-2025 The MITRE Corporation. -This project makes use of ATT&CK® +This project makes use of ATT&CK -[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) \ No newline at end of file +[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) diff --git a/docusaurus/docs/explanation/attack-specification-overview.md b/docusaurus/docs/explanation/attack-specification-overview.md index 2485133c..54c0d0cf 100644 --- a/docusaurus/docs/explanation/attack-specification-overview.md +++ b/docusaurus/docs/explanation/attack-specification-overview.md @@ -2,7 +2,7 @@ **Understanding the structure and purpose of the ATT&CK specification** -The ATT&CK specification defines the formal structure, semantics, and constraints that govern how MITRE ATT&CK® data is represented, validated, and consumed. Understanding the specification's design philosophy and architecture is crucial for working effectively with ATT&CK data and building robust applications that leverage the ATT&CK framework. +The ATT&CK specification defines the formal structure, semantics, and constraints that govern how MITRE ATT&CK data is represented, validated, and consumed. Understanding the specification's design philosophy and architecture is crucial for working effectively with ATT&CK data and building robust applications that leverage the ATT&CK framework. ## What is the ATT&CK Specification? diff --git a/docusaurus/docs/index.md b/docusaurus/docs/index.md index 702b5eda..8d58bd5f 100644 --- a/docusaurus/docs/index.md +++ b/docusaurus/docs/index.md @@ -1,6 +1,6 @@ # ATT&CK Data Model Documentation -*A TypeScript library for working with MITRE ATT&CK® data using STIX 2.1 bundles* +*A TypeScript library for working with MITRE ATT&CK data using STIX 2.1 bundles* Welcome to the documentation for the ATT&CK Data Model library. This documentation aims to provide you with exactly the right type of information for your needs, whether you are a beginner or a seasoned pro. diff --git a/docusaurus/docs/overview.md b/docusaurus/docs/overview.md index 1f4d108e..2b615180 100644 --- a/docusaurus/docs/overview.md +++ b/docusaurus/docs/overview.md @@ -1,12 +1,12 @@ # ATT&CK Data Model Overview -**A comprehensive TypeScript library for MITRE ATT&CK® data** +**A comprehensive TypeScript library for MITRE ATT&CK data** This page provides a high-level overview of the ATT&CK Data Model library architecture, its core concepts, and how all the pieces fit together. ## What is the ATT&CK Data Model? -The ATT&CK Data Model (ADM) is a TypeScript library that provides type-safe, programmatic access to MITRE ATT&CK® datasets. It bridges the gap between raw STIX 2.1 data and developer-friendly TypeScript objects. +The ATT&CK Data Model (ADM) is a TypeScript library that provides type-safe, programmatic access to MITRE ATT&CK datasets. It bridges the gap between raw STIX 2.1 data and developer-friendly TypeScript objects. ### Core Value Proposition diff --git a/docusaurus/docs/reference/api/index.md b/docusaurus/docs/reference/api/index.md index 1b1b615a..81779bac 100644 --- a/docusaurus/docs/reference/api/index.md +++ b/docusaurus/docs/reference/api/index.md @@ -2,7 +2,7 @@ **Complete documentation for all ATT&CK Data Model classes and functions** -The ATT&CK Data Model provides a comprehensive TypeScript API for working with MITRE ATT&CK® data. This reference section documents all public classes, methods, and functions available in the library. +The ATT&CK Data Model provides a comprehensive TypeScript API for working with MITRE ATT&CK data. This reference section documents all public classes, methods, and functions available in the library. ## Core API Components diff --git a/docusaurus/docs/tutorials/index.md b/docusaurus/docs/tutorials/index.md index ef4de03e..5c9213bc 100644 --- a/docusaurus/docs/tutorials/index.md +++ b/docusaurus/docs/tutorials/index.md @@ -2,7 +2,7 @@ **Learning-oriented guides for getting started with the ATT&CK Data Model** -Welcome to the tutorials section! These step-by-step guides will teach you how to work with the MITRE ATT&CK® Data Model (ADM) from the ground up. Each tutorial is designed to be completed by anyone with basic TypeScript knowledge, regardless of their familiarity with ATT&CK. +Welcome to the tutorials section! These step-by-step guides will teach you how to work with the MITRE ATT&CK Data Model (ADM) from the ground up. Each tutorial is designed to be completed by anyone with basic TypeScript knowledge, regardless of their familiarity with ATT&CK. ## What You'll Learn diff --git a/docusaurus/src/pages/index.tsx b/docusaurus/src/pages/index.tsx index 93be1c6c..66666837 100644 --- a/docusaurus/src/pages/index.tsx +++ b/docusaurus/src/pages/index.tsx @@ -13,13 +13,13 @@ function HomepageHeader() {
- {siteConfig.title} +

MITRE ATT&CK® Data Model

Documentation

- - Browse ATT&CK Schemas + + Get Started
From a204507f101ac3854f3470533b81d21e7a988741 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Sun, 10 Aug 2025 23:56:34 -0500 Subject: [PATCH 05/39] docs: add overview to sidebar --- docusaurus/docusaurus.config.ts | 4 ++-- docusaurus/sidebars.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docusaurus/docusaurus.config.ts b/docusaurus/docusaurus.config.ts index 9af672fb..d6f0c9cf 100644 --- a/docusaurus/docusaurus.config.ts +++ b/docusaurus/docusaurus.config.ts @@ -94,7 +94,7 @@ const config: Config = { docs: { sidebar: { hideable: true, - autoCollapseCategories: false, + autoCollapseCategories: true, }, }, footer: { @@ -156,7 +156,7 @@ const config: Config = { }, ], style: 'dark', - copyright: `© ${new Date().getFullYear()}, The MITRE Corporation. Built with Docusaurus.`, + copyright: `© ${new Date().getFullYear()}, The MITRE Corporation. Built with Docusaurus.`, }, prism: { theme: prismThemes.github, diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index ca749904..fc69f32d 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -7,6 +7,11 @@ const sidebars: SidebarsConfig = { id: 'index', label: 'Documentation Home' }, + { + type: 'doc', + id: 'overview', + label: 'Documentation Overview' + }, { type: 'category', label: 'Tutorials', From c0ca23c76b60fa7014ca760e40b8c6ce595c0328 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 11 Aug 2025 15:11:19 -0500 Subject: [PATCH 06/39] docs: add contributing guides --- .../contributing/coding-style.md | 44 ++ .../how-to-guides/contributing/dev-setup.md | 31 ++ .../docs/how-to-guides/contributing/docs.md | 27 ++ .../docs/how-to-guides/contributing/index.md | 15 + .../docs/how-to-guides/contributing/tests.md | 38 ++ .../docs/how-to-guides/extend-schemas.md | 441 ------------------ docusaurus/docs/how-to-guides/index.md | 7 +- docusaurus/sidebars.ts | 8 +- .../src/components/DocTypeIndicator/index.tsx | 26 +- package-lock.json | 291 ++++++++++++ package.json | 2 + vitest.config.ts | 22 +- 12 files changed, 483 insertions(+), 469 deletions(-) create mode 100644 docusaurus/docs/how-to-guides/contributing/coding-style.md create mode 100644 docusaurus/docs/how-to-guides/contributing/dev-setup.md create mode 100644 docusaurus/docs/how-to-guides/contributing/docs.md create mode 100644 docusaurus/docs/how-to-guides/contributing/index.md create mode 100644 docusaurus/docs/how-to-guides/contributing/tests.md delete mode 100644 docusaurus/docs/how-to-guides/extend-schemas.md diff --git a/docusaurus/docs/how-to-guides/contributing/coding-style.md b/docusaurus/docs/how-to-guides/contributing/coding-style.md new file mode 100644 index 00000000..09f2120d --- /dev/null +++ b/docusaurus/docs/how-to-guides/contributing/coding-style.md @@ -0,0 +1,44 @@ +# Coding Style & Linting + +ADM enforces a **single source-of-truth** for style: + +* **ESLint** – `@eslint/js` + custom rules +* **Prettier** – code formatting +* **commitlint** – conventional-commits + +## ESLint + Prettier + +```bash +npm run lint # read-only +npm run format # auto-fix + prettier +``` + +CI will fail if `npm run lint` reports errors or if Prettier formatting changes are uncommitted. + +## Typical workflow + +```bash +git checkout -b feat/my-awesome-change +# …code… +npm run format +git add . +git commit -m "feat(core): add awesome change" +``` + +The commit message is checked by [commitlint](https://commitlint.js.org). +Prefix your commit with one of the following: + +| Prefix | Description | Example Commit Command | +|----------|------------------------------------------------|----------------------------------------------------------| +| feat | A new feature | `git commit -m "feat: add new schema"` | +| fix | A bug fix | `git commit -m "fix: correct null pointer exception"` | +| docs | Documentation only changes | `git commit -m "docs: update installation instructions"` | +| chore | Routine tasks, maintenance, or tooling | `git commit -m "chore: update dependency versions"` | +| refactor | Code changes that neither fix nor add features | `git commit -m "refactor: simplify token validation"` | +| test | Adding or updating tests | `git commit -m "test: add tests for date parser"` | +| perf | Performance improvements | `git commit -m "perf: optimize query performance"` | + +## TypeScript strictness + +The library is compiled with `"strict": true` and imports must be path-alias aware (`@/…`). +Run `npm run build` to catch any type errors locally. diff --git a/docusaurus/docs/how-to-guides/contributing/dev-setup.md b/docusaurus/docs/how-to-guides/contributing/dev-setup.md new file mode 100644 index 00000000..308a39b1 --- /dev/null +++ b/docusaurus/docs/how-to-guides/contributing/dev-setup.md @@ -0,0 +1,31 @@ +# Development Setup + +Follow these steps to get the ADM codebase running on your local machine. + +## Clone and install + +```bash +git clone https://github.com/mitre-attack/attack-data-model.git +cd attack-data-model +npm install +``` + +💡 The repo is a multi-package workspace (core library + docusaurus site). Running npm install at the root will also bootstrap the docs workspace. + +## Verify TypeScript build + +```bash +npm run build # compiles src → dist using tsup +npm run test # vitest smoke-test +``` + +## Handy npm scripts + +| Script | Purpose | +|---|---| +| npm run lint | Lints all src/** files | +| npm run format | Prettier + ESLint fix | +| npm run test | Run tests | + +You are now ready to hack on the library. +Continue with [Coding Style & Linting](coding-style.md) for the mandatory style rules. diff --git a/docusaurus/docs/how-to-guides/contributing/docs.md b/docusaurus/docs/how-to-guides/contributing/docs.md new file mode 100644 index 00000000..3c8720cc --- /dev/null +++ b/docusaurus/docs/how-to-guides/contributing/docs.md @@ -0,0 +1,27 @@ +# Updating Documentation + +## Auto-generating schema reference files + +The script `generate-docs.sh` introspects **all** `*.schema.ts` files and +emits Markdown to `docusaurus/docs/reference/schemas/*` which is git ignored. + +```bash +cd docusaurus +npm run gendocs +``` + +## Run the documentation site locally (optional) + +```bash +npm start # http://localhost:3000 +``` + +## Adding new how-to guides + +1. Create your Markdown file inside `docusaurus/docs//my-guide.md` +2. Update `sidebars.ts` or rely on *autogenerated* sidebars by placing the file in an existing directory. + +## Writing reference pages + +For API reference (classes, functions), prefer TypeDoc or embed short code blocks. +Long-form tutorials belong under `docs/tutorials/`. diff --git a/docusaurus/docs/how-to-guides/contributing/index.md b/docusaurus/docs/how-to-guides/contributing/index.md new file mode 100644 index 00000000..3002af4a --- /dev/null +++ b/docusaurus/docs/how-to-guides/contributing/index.md @@ -0,0 +1,15 @@ +# Contributing to the ATT&CK Data Model (ADM) + +**Step-by-step technical instructions for contributors** + +This section explains how to contribute to the ATT&CK Data Model repository. +It complements the high-level `CONTRIBUTING.md` in the root of the repository by focusing on specific developer tasks. + +## Sub-topics + +| Guide | What you’ll learn | +|-------|-------------------| +| [Development Setup](./dev-setup) | Cloning, installing dependencies, and common npm scripts | +| [Coding Style & Linting](./coding-style) | ESLint, Prettier, and commit-lint rules | +| [Running and Writing Tests](./tests) | Vitest workflow, coverage, and test helpers | +| [Updating Docs & Schemas](./docs) | Auto-generating docs from Zod schemas and writing how-to guides | diff --git a/docusaurus/docs/how-to-guides/contributing/tests.md b/docusaurus/docs/how-to-guides/contributing/tests.md new file mode 100644 index 00000000..d17b0826 --- /dev/null +++ b/docusaurus/docs/how-to-guides/contributing/tests.md @@ -0,0 +1,38 @@ +# Running and Writing Tests + +The project uses **[Vitest](https://vitest.dev/)**. + +## Execute the full suite + +```bash +npm test +``` + +## Watch mode + +```bash +npm run test:interactive +``` + +Vitest is configured in `vitest.config.ts` with: + +- Node environment +- Global `describe/it/expect` +- Path alias `@` → `./src` + +## Coverage + +```bash +npm run test:coverage +``` + +Coverage reports are not required for PRs but strongly encouraged for new functionality. + +## Test helpers + +- `test/vitest.setup.ts` – global test framework setup + +When adding new schemas or refinement logic: + +1. Write **positive** test cases (`schema.parse` passes) +2. Write **negative** test cases that assert specific error messages diff --git a/docusaurus/docs/how-to-guides/extend-schemas.md b/docusaurus/docs/how-to-guides/extend-schemas.md deleted file mode 100644 index 3e7f92ed..00000000 --- a/docusaurus/docs/how-to-guides/extend-schemas.md +++ /dev/null @@ -1,441 +0,0 @@ -# How to Extend Schemas with Custom Fields - -**Add your own properties to ATT&CK objects while preserving validation** - -When building applications with the ATT&CK Data Model, you may need to add custom fields to ATT&CK objects for your specific use case. This guide shows you how to extend the provided schemas while maintaining all existing validation rules. - -## Problem - -You want to: - -- Add custom fields to ATT&CK objects (techniques, groups, etc.) -- Preserve all existing ATT&CK validation rules and refinements -- Use the extended schemas in your application -- Maintain type safety with TypeScript - -## Solution Overview - -Extend ATT&CK schemas using Zod's extension methods while manually reapplying the original refinements that would otherwise be lost. - -## Step 1: Understanding Schema Refinements - -ATT&CK schemas use Zod refinements for advanced validation. When you extend a schema, these refinements are discarded: - -```typescript -import { campaignSchema } from '@mitre-attack/attack-data-model'; - -// ❌ This loses the original refinements -const extendedCampaign = campaignSchema.extend({ - customField: z.string() -}); - -// ✅ This preserves refinements (we'll show how below) -``` - -First, check which refinements a schema uses by examining its source file or the exported refinement functions. - -## Step 2: Extending Campaigns with Custom Fields - -Let's extend the campaign schema with custom tracking fields: - -```typescript -import { - campaignSchema, - createFirstAliasRefinement, - createCitationsRefinement -} from '@mitre-attack/attack-data-model'; -import { z } from 'zod'; - -// Define your custom fields -const customCampaignFields = z.object({ - x_custom_severity: z.enum(['low', 'medium', 'high', 'critical']).optional(), - x_custom_industry_targets: z.array(z.string()).optional(), - x_custom_confidence_score: z.number().min(0).max(100).optional(), - x_custom_tracking_id: z.string().optional() -}); - -// Extend the schema with custom fields -const extendedCampaignSchema = campaignSchema - .extend(customCampaignFields.shape) - .superRefine((data, ctx) => { - // Reapply the original refinements - createFirstAliasRefinement()(data, ctx); - createCitationsRefinement()(data, ctx); - - // Add custom validation rules - if (data.x_custom_confidence_score !== undefined && - data.x_custom_confidence_score > 50 && - !data.x_custom_severity) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "High confidence campaigns must specify severity", - path: ['x_custom_severity'] - }); - } - }); - -// Create TypeScript type from extended schema -type ExtendedCampaign = z.infer; -``` - -## Step 3: Extending Techniques with Metadata - -Extend technique schemas for custom threat intelligence: - -```typescript -import { - techniqueSchema, - createTacticRefinement, - createAttackIdRefinement, - createCitationsRefinement -} from '@mitre-attack/attack-data-model'; - -const customTechniqueFields = z.object({ - x_custom_detection_difficulty: z.enum(['easy', 'medium', 'hard']).optional(), - x_custom_business_impact: z.number().min(1).max(5).optional(), - x_custom_last_seen: z.string().datetime().optional(), - x_custom_tags: z.array(z.string()).optional(), - x_custom_iocs: z.array(z.object({ - type: z.enum(['hash', 'ip', 'domain', 'file_path']), - value: z.string(), - confidence: z.number().min(0).max(100) - })).optional() -}); - -const extendedTechniqueSchema = techniqueSchema - .extend(customTechniqueFields.shape) - .superRefine((data, ctx) => { - // Reapply original refinements - createTacticRefinement()(data, ctx); - createAttackIdRefinement()(data, ctx); - createCitationsRefinement()(data, ctx); - - // Custom validation: ensure IoCs have valid formats - if (data.x_custom_iocs) { - data.x_custom_iocs.forEach((ioc, index) => { - if (ioc.type === 'ip' && !isValidIP(ioc.value)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Invalid IP address format", - path: ['x_custom_iocs', index, 'value'] - }); - } - }); - } - }); - -type ExtendedTechnique = z.infer; - -// Helper validation function -function isValidIP(ip: string): boolean { - const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; - const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; - return ipv4Regex.test(ip) || ipv6Regex.test(ip); -} -``` - -## Step 4: Creating a Schema Extension Factory - -Build a reusable factory for extending any ATT&CK schema: - -```typescript -interface SchemaExtensionConfig { - customFields: z.ZodRawShape; - refinements: Array<(data: any, ctx: z.RefinementCtx) => void>; - customValidation?: (data: T, ctx: z.RefinementCtx) => void; -} - -function extendAttackSchema( - baseSchema: z.ZodObject, - config: SchemaExtensionConfig -) { - return baseSchema - .extend(config.customFields) - .superRefine((data, ctx) => { - // Apply original refinements - config.refinements.forEach(refinement => { - refinement(data, ctx); - }); - - // Apply custom validation if provided - if (config.customValidation) { - config.customValidation(data as U, ctx); - } - }); -} - -// Usage examples -const extendedGroupSchema = extendAttackSchema(groupSchema, { - customFields: { - x_custom_threat_level: z.enum(['nation-state', 'criminal', 'hacktivist']).optional(), - x_custom_active_since: z.string().datetime().optional() - }, - refinements: [ - createFirstAliasRefinement(), - createCitationsRefinement() - ], - customValidation: (data, ctx) => { - if (data.x_custom_threat_level === 'nation-state' && - !data.x_custom_active_since) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Nation-state groups must have active_since date", - path: ['x_custom_active_since'] - }); - } - } -}); -``` - -## Step 5: Working with Extended Schemas - -Use your extended schemas in applications: - -```typescript -// Validate data with extended schema -function validateExtendedCampaign(data: unknown): ExtendedCampaign { - try { - return extendedCampaignSchema.parse(data); - } catch (error) { - if (error instanceof z.ZodError) { - console.error('Validation errors:'); - error.errors.forEach(err => { - console.error(`${err.path.join('.')}: ${err.message}`); - }); - } - throw error; - } -} - -// Example usage with custom data -const customCampaignData = { - // Standard ATT&CK fields - type: "campaign", - spec_version: "2.1", - id: "campaign--12345678-1234-1234-1234-123456789012", - created: "2023-01-01T00:00:00.000Z", - modified: "2023-01-01T00:00:00.000Z", - name: "Custom Campaign", - description: "A campaign with custom fields", - x_mitre_attack_spec_version: "3.3.0", - x_mitre_version: "1.0", - - // Your custom fields - x_custom_severity: "high", - x_custom_industry_targets: ["finance", "healthcare"], - x_custom_confidence_score: 85, - x_custom_tracking_id: "TRACK-2023-001" -}; - -const validatedCampaign = validateExtendedCampaign(customCampaignData); -console.log(validatedCampaign.x_custom_severity); // TypeScript knows this exists! -``` - -## Step 6: Creating Custom Implementation Classes - -Extend the implementation classes to work with your custom fields: - -```typescript -import { CampaignImpl } from '@mitre-attack/attack-data-model'; - -class ExtendedCampaignImpl extends CampaignImpl { - // Custom methods for your extended fields - getSeverityLevel(): string { - return (this as any).x_custom_severity || 'unknown'; - } - - getIndustryTargets(): string[] { - return (this as any).x_custom_industry_targets || []; - } - - isHighConfidence(): boolean { - const score = (this as any).x_custom_confidence_score; - return score !== undefined && score >= 80; - } - - // Custom business logic - calculateRiskScore(): number { - const severityWeight = { - 'low': 1, - 'medium': 2, - 'high': 3, - 'critical': 4 - }; - - const severity = this.getSeverityLevel(); - const confidence = (this as any).x_custom_confidence_score || 0; - - return (severityWeight[severity] || 0) * (confidence / 100); - } -} - -// Factory function to create extended instances -function createExtendedCampaign(data: ExtendedCampaign): ExtendedCampaignImpl { - return new ExtendedCampaignImpl(data); -} -``` - -## Step 7: Integrating with Data Sources - -Load custom data with extended schemas: - -```typescript -import { DataSource } from '@mitre-attack/attack-data-model'; - -class ExtendedDataProcessor { - async processCustomBundle(filePath: string) { - // Load the bundle with standard data source - const dataSource = new DataSource({ - source: 'file', - file: filePath, - parsingMode: 'relaxed' // Allow custom fields - }); - - const uuid = await registerDataSource(dataSource); - const attackDataModel = loadDataModel(uuid); - - // Process campaigns with extended validation - const extendedCampaigns = attackDataModel.campaigns.map(campaign => { - try { - // Validate against extended schema - const validatedData = extendedCampaignSchema.parse(campaign); - return createExtendedCampaign(validatedData); - } catch (error) { - console.warn(`Campaign ${campaign.name} failed extended validation:`, error); - return null; - } - }).filter(Boolean) as ExtendedCampaignImpl[]; - - return extendedCampaigns; - } -} -``` - -## Step 8: Schema Composition for Complex Extensions - -Combine multiple extensions for comprehensive customization: - -```typescript -// Base threat intelligence fields -const threatIntelFields = z.object({ - x_custom_confidence: z.number().min(0).max(100), - x_custom_source: z.string(), - x_custom_last_updated: z.string().datetime() -}); - -// Organization-specific fields -const orgSpecificFields = z.object({ - x_org_priority: z.enum(['p1', 'p2', 'p3', 'p4']), - x_org_assigned_analyst: z.string().optional(), - x_org_notes: z.array(z.object({ - timestamp: z.string().datetime(), - author: z.string(), - content: z.string() - })).optional() -}); - -// Compose multiple extensions -const fullyExtendedTechniqueSchema = techniqueSchema - .extend({ - ...threatIntelFields.shape, - ...orgSpecificFields.shape, - ...customTechniqueFields.shape - }) - .superRefine((data, ctx) => { - // Apply all refinements and validations - createTacticRefinement()(data, ctx); - createAttackIdRefinement()(data, ctx); - createCitationsRefinement()(data, ctx); - - // Cross-field validation - if (data.x_org_priority === 'p1' && !data.x_org_assigned_analyst) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "P1 techniques must have assigned analyst", - path: ['x_org_assigned_analyst'] - }); - } - }); - -type FullyExtendedTechnique = z.infer; -``` - -## Common Pitfalls - -### Forgetting Refinements - -Always check the original schema file to see which refinements to reapply: - -```typescript -// Look in the source schema file for patterns like: -// .superRefine(createSomeRefinement()) -// .check(someValidationFunction) -``` - -### Invalid Custom Field Names - -Follow STIX custom property naming conventions: - -```typescript -// ✅ Correct - use your namespace prefix -x_myorg_custom_field: z.string() - -// ❌ Incorrect - generic x_custom may conflict -x_custom_field: z.string() -``` - -### Type Safety Issues - -Ensure TypeScript integration works correctly: - -```typescript -// Create proper type exports -export type ExtendedCampaign = z.infer; -export const extendedCampaignSchema = /* your schema */; - -// Use type assertions carefully in implementation classes -class ExtendedImpl extends BaseImpl { - getCustomField(): string { - return (this as ExtendedCampaign).x_custom_field || ''; - } -} -``` - -## Testing Extended Schemas - -Create comprehensive tests for your extensions: - -```typescript -import { describe, it, expect } from 'vitest'; - -describe('Extended Campaign Schema', () => { - it('should validate campaigns with custom fields', () => { - const validCampaign = { - // ... standard fields - x_custom_severity: 'high', - x_custom_confidence_score: 90 - }; - - expect(() => extendedCampaignSchema.parse(validCampaign)).not.toThrow(); - }); - - it('should enforce custom validation rules', () => { - const invalidCampaign = { - // ... standard fields - x_custom_confidence_score: 95 // High confidence without severity - }; - - expect(() => extendedCampaignSchema.parse(invalidCampaign)).toThrow(); - }); -}); -``` - ---- - -## Next Steps - -- **Validation**: Learn how to [validate custom bundles](./validate-bundles) with your extended schemas -- **Error Handling**: Implement [robust error handling](./error-handling) for validation failures -- **Reference**: Explore the [complete schema API](../reference/schemas/) - -Your extended schemas now preserve all ATT&CK validation while supporting your custom use cases! diff --git a/docusaurus/docs/how-to-guides/index.md b/docusaurus/docs/how-to-guides/index.md index e039b2d9..8cd5d41e 100644 --- a/docusaurus/docs/how-to-guides/index.md +++ b/docusaurus/docs/how-to-guides/index.md @@ -11,9 +11,12 @@ These guides provide direct solutions to common problems you'll encounter when w - **[How to validate custom STIX bundles](./validate-bundles)** - Ensure your custom ATT&CK data meets specification requirements - **[How to handle parsing errors gracefully](./error-handling)** - Implement robust error handling for production applications -### Customization & Extension +### Contributing -- **[How to extend schemas with custom fields](./extend-schemas)** - Add your own properties to ATT&CK objects while preserving validation +- **[Development setup](./contributing/dev-setup)** - Clone, install, and run the project locally +- **[Coding style & linting](./contributing/coding-style)** - ESLint, Prettier, commit rules +- **[Running and writing tests](./contributing/tests)** - Vitest workflow and coverage +- **[Updating docs & schemas](./contributing/docs)** - Auto-generating docs and writing guides ## Quick Solutions diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index fc69f32d..c5be278d 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -5,12 +5,12 @@ const sidebars: SidebarsConfig = { { type: 'doc', id: 'index', - label: 'Documentation Home' + label: 'Documentation Home', }, { type: 'doc', id: 'overview', - label: 'Documentation Overview' + label: 'Documentation Overview', }, { type: 'category', @@ -32,6 +32,10 @@ const sidebars: SidebarsConfig = { type: 'autogenerated', dirName: 'how-to-guides', }, + { + type: 'autogenerated', + dirName: 'how-to-guides/contributing', + }, ], }, { diff --git a/docusaurus/src/components/DocTypeIndicator/index.tsx b/docusaurus/src/components/DocTypeIndicator/index.tsx index 77406b24..efc3c2f1 100644 --- a/docusaurus/src/components/DocTypeIndicator/index.tsx +++ b/docusaurus/src/components/DocTypeIndicator/index.tsx @@ -52,15 +52,13 @@ export default function DocTypeIndicator({ return (
{config.emoji} @@ -111,10 +109,12 @@ export function DocTypeCard({
diff --git a/package-lock.json b/package-lock.json index 034aa150..092d746a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@eslint/js": "^9.28.0", "@semantic-release/git": "^10.0.1", "@types/uuid": "^10.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.4.1", @@ -34,6 +35,31 @@ "zod2md": "^0.2.2" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -49,6 +75,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", @@ -59,6 +95,46 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1081,6 +1157,16 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", @@ -2581,6 +2667,40 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -2860,6 +2980,36 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", + "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.29", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5103,6 +5253,13 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5429,6 +5586,71 @@ "node": "^18.17 || >=20.6.1" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -5806,6 +6028,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -10511,6 +10761,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", diff --git a/package.json b/package.json index 5d280884..ea3568a8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "build": "npx tsup --dts --format esm,cjs src", "prepublishOnly": "rm -rf dist && npm run build", "test": "vitest --run", + "test:coverage": "vitest --run --coverage", "test:interactive": "vitest", "export": "npm pack", "clean": "rm -rf test/**/*.js test/**/*.js.map test/**/*.d.ts test/**/*.ts.map src/**/*.js src/**/*.js.map src/**/*.d.ts examples/**/*.js examples/**/*.js.map dist", @@ -92,6 +93,7 @@ "@eslint/js": "^9.28.0", "@semantic-release/git": "^10.0.1", "@types/uuid": "^10.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.4.1", diff --git a/vitest.config.ts b/vitest.config.ts index 162abed2..3849330a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,15 +2,15 @@ import { defineConfig } from 'vitest/config'; import path from 'path'; export default defineConfig({ - test: { - environment: 'node', - globals: true, - include: ['test/**/*.test.ts'], - setupFiles: ['./test/vitest.setup.ts'], + test: { + environment: 'node', + globals: true, + include: ['test/**/*.test.ts'], + setupFiles: ['./test/vitest.setup.ts'], + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src') - } - }, -}); \ No newline at end of file + }, +}); From 904c06aac499bdc571273e743e43dbcf62fe682f Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 11 Aug 2025 15:15:21 -0500 Subject: [PATCH 07/39] docs: rename contributing guide title --- docusaurus/docs/how-to-guides/contributing/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/how-to-guides/contributing/index.md b/docusaurus/docs/how-to-guides/contributing/index.md index 3002af4a..c1492e0a 100644 --- a/docusaurus/docs/how-to-guides/contributing/index.md +++ b/docusaurus/docs/how-to-guides/contributing/index.md @@ -1,4 +1,4 @@ -# Contributing to the ATT&CK Data Model (ADM) +# Contributing **Step-by-step technical instructions for contributors** From 3f3a3ef03419fe4c1820cefda7a1ee3cab6f344b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:31:43 -0400 Subject: [PATCH 08/39] docs: update readme - Fix code references to DataSourceRegistration - Reformat section about the Docusaurus site --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 425cf219..73e40f6f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ The ATT&CK Data Model (ADM) provides a type-safe, object-oriented interface for working with MITRE ATT&CK datasets. Built on STIX 2.1 compliance, it uses Zod schemas and TypeScript types to ensure data integrity while providing intuitive relationship navigation between ATT&CK objects. +**[CLICK HERE](https://mitre-attack.github.io/attack-data-model) [1](#footnotes)** to browse the ATT&CK schemas in a user-friendly interface. + ## Key Features - **Type-Safe Data Parsing**: ADM validates STIX 2.1 bundles using Zod schemas, ensuring data model compliance and type safety. @@ -88,9 +90,9 @@ For most users, we recommend: Example of loading the latest ATT&CK data: ```javascript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; -const dataSource = new DataSource({ +const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', version: '17.1', @@ -130,12 +132,12 @@ For additional context about the ATT&CK specification, please refer to the [ATT& Here's an example script that demonstrates how to use the ADM library to load ATT&CK data from the official MITRE ATT&CK GitHub repository: ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; (async () => { - // Instantiating a DataSource object will validate that the data source is accessible and readable - const dataSource = new DataSource({ + // Instantiating a DataSourceRegistration object will validate that the data source is accessible and readable + const dataSource = new DataSourceRegistration({ source: 'attack', // Built-in index to retrieve ATT&CK content from the official MITRE ATT&CK STIX 2.1 GitHub repository domain: 'enterprise-attack', version: '15.1', // Omitting 'version' will default to the latest version available in the repository @@ -234,6 +236,10 @@ Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-mod We welcome contributions! Please see our [CONTRIBUTING.md](./docs/CONTRIBUTING.md) file for details on how to contribute to this project. +## Footnotes + +1 The [schemas site](https://mitre-attack.github.io/attack-data-model) is dynamically generated from the contents of the `@latest` distribution channel / `main` branch. We do not currently maintain separate documentation for previous releases, though we hope to in the future. + ## License This project is licensed under the Apache 2.0 License. From 9dcda5ed99b09be627462e417e6a524da9292337 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 14 Aug 2025 16:55:56 -0500 Subject: [PATCH 09/39] chore: remove old examples and add one new one --- examples/README.md | 7 +- examples/common/x-mitre-domains.example.ts | 66 --- examples/first-query.ts | 41 ++ examples/sdo/analytic.example.ts | 505 -------------------- examples/sdo/asset.example.ts | 229 --------- examples/sdo/campaign.example.ts | 374 --------------- examples/sdo/collection.example.ts | 134 ------ examples/sdo/data-component.example.ts | 139 ------ examples/sdo/data-source.example.ts | 166 ------- examples/sdo/detection-strategy.example.ts | 456 ------------------ examples/sdo/group.example.ts | 171 ------- examples/sdo/identity.example.ts | 113 ----- examples/sdo/log-source.example.ts | 523 --------------------- examples/sdo/malware.example.ts | 260 ---------- examples/sdo/matrix.example.ts | 181 ------- examples/sdo/mitigation.example.ts | 149 ------ examples/sdo/software.example.ts | 490 ------------------- examples/sdo/stix-bundle.example.ts | 298 ------------ examples/sdo/tactic.example.ts | 121 ----- examples/sdo/technique.example.ts | 436 ----------------- examples/smo/marking-definition.example.ts | 175 ------- tsconfig.json | 9 +- 22 files changed, 49 insertions(+), 4994 deletions(-) delete mode 100644 examples/common/x-mitre-domains.example.ts create mode 100644 examples/first-query.ts delete mode 100644 examples/sdo/analytic.example.ts delete mode 100644 examples/sdo/asset.example.ts delete mode 100644 examples/sdo/campaign.example.ts delete mode 100644 examples/sdo/collection.example.ts delete mode 100644 examples/sdo/data-component.example.ts delete mode 100644 examples/sdo/data-source.example.ts delete mode 100644 examples/sdo/detection-strategy.example.ts delete mode 100644 examples/sdo/group.example.ts delete mode 100644 examples/sdo/identity.example.ts delete mode 100644 examples/sdo/log-source.example.ts delete mode 100644 examples/sdo/malware.example.ts delete mode 100644 examples/sdo/matrix.example.ts delete mode 100644 examples/sdo/mitigation.example.ts delete mode 100644 examples/sdo/software.example.ts delete mode 100644 examples/sdo/stix-bundle.example.ts delete mode 100644 examples/sdo/tactic.example.ts delete mode 100644 examples/sdo/technique.example.ts delete mode 100644 examples/smo/marking-definition.example.ts diff --git a/examples/README.md b/examples/README.md index cebab827..9d3649b8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,9 +1,10 @@ # Examples -This directory contains example TypeScript modules demonstrating common use cases and expected outputs for true-positive and true-negative scenarios, adapted for each of the provided ATT&CK Zod schemas. +This directory contains example TypeScript modules demonstrating common use cases for using the ATT&CK Data Model. +Read more about them in the documentation! To run them, run `npx tsx` from the project root directory: ```bash -npx tsx examples/sdo/tactic.example.ts -``` \ No newline at end of file +npx tsx examples/first-query.ts +``` diff --git a/examples/common/x-mitre-domains.example.ts b/examples/common/x-mitre-domains.example.ts deleted file mode 100644 index 5dc640d4..00000000 --- a/examples/common/x-mitre-domains.example.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { z } from 'zod'; -import { xMitreDomainsSchema } from '../../src/schemas/common/index.js'; - - -const ExampleObjectSchema = z.object({ - x_mitre_domains: xMitreDomainsSchema -}); - -// Test function for individual arrays -function testArrayCase(testCase: any[]) { - console.log(`Testing array: ${JSON.stringify(testCase)}`); - try { - const result = xMitreDomainsSchema.parse(testCase); - console.log(`SUCCESS: Parsed ${JSON.stringify(result)}`); - } catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } else { - console.log("Unexpected error:", error); - } - } - console.log("---"); -} - -// Test function for objects -function testObjectCase(testCase: any) { - console.log(`Testing object: ${JSON.stringify(testCase)}`); - try { - ExampleObjectSchema.parse(testCase); - console.log(`SUCCESS: Parsed ${JSON.stringify(testCase)}`); - } catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } else { - console.log("Unexpected error:", error); - } - } - console.log("---"); -} - -// Main test function -function runTests() { - console.log("Testing individual arrays:"); - const arrayCases = [ - ["enterprise-attack"], - ["foobar"], - ["enterprise-attack", "mobile-attack"], - ["enterprise-attack", "foobar"], - [] - ]; - arrayCases.forEach(testArrayCase); - - console.log("\nTesting objects:"); - const objectCases = [ - { x_mitre_domains: ["enterprise-attack"] }, - { x_mitre_domains: ["foobar"] }, - { x_mitre_domains: ["enterprise-attack", "mobile-attack"] }, - { x_mitre_domains: ["enterprise-attack", "foobar"] }, - { x_mitre_domains: [] }, - {} - ]; - objectCases.forEach(testObjectCase); -} - -// Run the tests -runTests(); \ No newline at end of file diff --git a/examples/first-query.ts b/examples/first-query.ts new file mode 100644 index 00000000..32ece5c9 --- /dev/null +++ b/examples/first-query.ts @@ -0,0 +1,41 @@ +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + +async function exploreAttackData() { + console.log('🎯 Loading ATT&CK Enterprise data...\n'); + + // Step 1: Create a data source + const dataSource = new DataSourceRegistration({ + source: 'attack', // Load from official ATT&CK repository + domain: 'enterprise-attack', // Focus on Enterprise domain + version: '15.1', // Use specific version for consistency + parsingMode: 'relaxed', // Continue even if some data has minor issues + }); + + try { + // Step 2: Register the data source + const uuid = await registerDataSource(dataSource); + + if (uuid) { + console.log('✅ Data source registered successfully!\n'); + + // Step 3: Load the data model + const attackDataModel = loadDataModel(uuid); + console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); + + // Step 4: Explore the first few techniques + console.log('🔍 First 5 techniques:'); + attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { + console.log( + `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`, + ); + }); + } else { + console.error('❌ Failed to register data source'); + } + } catch (error) { + console.error('❌ Error:', error); + } +} + +// Run the function +exploreAttackData(); diff --git a/examples/sdo/analytic.example.ts b/examples/sdo/analytic.example.ts deleted file mode 100644 index 695e76aa..00000000 --- a/examples/sdo/analytic.example.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import { analyticSchema } from "../../src/schemas/sdo/analytic.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Analytic -/*************************************************************************************************** */ -const validAnalytic = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43d", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Suspicious PowerShell Activity", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0001", - "url": "https://attack.mitre.org/analytics/AN0001" - } - ], - "x_mitre_detects": "Adversary execution of PowerShell commands with suspicious parameters", - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["PowerShell"] - } - ], - "x_mitre_platforms": ["Windows"], - "x_mitre_mutable_elements": [ - { - "field": "TimeWindow", - "description": "Time window for correlation analysis" - } - ] -}; - -console.log("\nExample 1 - Valid Analytic:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalytic).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Analytic (ATT&CK ID does not match format AN####) -/*************************************************************************************************** */ -const invalidAnalyticID = { - ...validAnalytic, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/analytics/AN0001", - "external_id": "A001" - } - ], -}; - -console.log("\nExample 2 - Invalid Analytic (ATT&CK ID does not match format AN####):"); -try { - analyticSchema.parse(invalidAnalyticID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ The first external_reference must match the ATT&CK ID format AN####. -// → at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Valid Analytic with Multiple Log Sources and Mutable Elements -/*************************************************************************************************** */ -const validAnalyticMultipleElements = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43e", - "name": "Multi-Layer Process Monitoring", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0002", - "url": "https://attack.mitre.org/analytics/AN0002" - } - ], - "x_mitre_detects": "Adversary process creation and execution patterns across multiple log sources", - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["Sysmon", "EventCode=1"] - }, - { - "ref": "x-mitre-log-source--6ba7b811-9dad-11d1-80b4-00c04fd430c9", - "keys": ["Security", "EventCode=4688"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "TimeWindow", - "description": "Time window for correlation analysis" - }, - { - "field": "UserContext", - "description": "User context for filtering" - }, - { - "field": "ProcessNamePattern", - "description": "Regular expression pattern for process names" - } - ] -}; - -console.log("\nExample 3 - Valid Analytic with Multiple Elements:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticMultipleElements).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Analytic (missing required fields) -/*************************************************************************************************** */ -const invalidAnalyticMissingFields = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43f", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - // Missing name - // Missing created_by_ref (required by schema) - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // Missing object_marking_refs (required by schema) - // Missing x_mitre_domains - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0003", - "url": "https://attack.mitre.org/analytics/AN0003" - } - ], - // Missing x_mitre_detects - // Missing x_mitre_log_sources - // Missing x_mitre_mutable_elements -}; - -console.log("\nExample 4 - Invalid Analytic (missing required fields):"); -try { - analyticSchema.parse(invalidAnalyticMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Unrecognized key: "x_mitre_modified_by_ref" -// ✖ Invalid input: expected nonoptional, received undefined -// → at created_by_ref -// ✖ Invalid input: expected nonoptional, received undefined -// → at object_marking_refs -// ✖ Invalid input: expected string, received undefined -// → at name -// ✖ x_mitre_platforms must be an array of strings -// → at x_mitre_platforms -// ✖ Invalid input: expected string, received undefined -// → at x_mitre_detects -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_log_sources -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_mutable_elements -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_domains - -/*************************************************************************************************** */ -// Example 5: Invalid Analytic with invalid type -/*************************************************************************************************** */ -const analyticWithInvalidType = { - ...validAnalytic, - "type": 'invalid-type' -} - -console.log("\nExample 5 - Analytic with invalid type:"); -try { - analyticSchema.parse(analyticWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected "x-mitre-analytic" -// → at type - -/*************************************************************************************************** */ -// Example 6: Invalid Analytic (empty log sources array) -/*************************************************************************************************** */ -const invalidAnalyticEmptyLogSources = { - ...validAnalytic, - "x_mitre_log_sources": [] -}; - -console.log("\nExample 6 - Invalid Analytic (empty log sources array):"); -try { - analyticSchema.parse(invalidAnalyticEmptyLogSources); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected array to have >=1 items -// → at x_mitre_log_sources - -/*************************************************************************************************** */ -// Example 7: Invalid Analytic (duplicate log source refs) -/*************************************************************************************************** */ -const invalidAnalyticDuplicateLogSources = { - ...validAnalytic, - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["PowerShell"] - }, - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["Security"] - } - ] -}; - -console.log("\nExample 7 - Invalid Analytic (duplicate log source refs):"); -try { - analyticSchema.parse(invalidAnalyticDuplicateLogSources); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Duplicate log source permutation found: each (name, channel) pair must be unique -// → at x_mitre_log_sources.x_mitre_log_source_permutations - -/*************************************************************************************************** */ -// Example 8: Invalid Analytic (empty mutable elements array) -/*************************************************************************************************** */ -const invalidAnalyticEmptyMutableElements = { - ...validAnalytic, - "x_mitre_mutable_elements": [] -}; - -console.log("\nExample 8 - Invalid Analytic (empty mutable elements array):"); -try { - analyticSchema.parse(invalidAnalyticEmptyMutableElements); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected array to have >=1 items -// → at x_mitre_mutable_elements - -/*************************************************************************************************** */ -// Example 9: Invalid Analytic (empty detects field) -/*************************************************************************************************** */ -const invalidAnalyticEmptyDetects = { - ...validAnalytic, - "x_mitre_detects": "" -}; - -console.log("\nExample 9 - Invalid Analytic (empty detects field):"); -try { - analyticSchema.parse(invalidAnalyticEmptyDetects); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected string to have >=1 characters -// → at x_mitre_detects - -/*************************************************************************************************** */ -// Example 10: Invalid Analytic (empty log source keys) -/*************************************************************************************************** */ -const invalidAnalyticEmptyKeys = { - ...validAnalytic, - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": [] - } - ] -}; - -console.log("\nExample 10 - Invalid Analytic (empty log source keys):"); -try { - analyticSchema.parse(invalidAnalyticEmptyKeys); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected array to have >=1 items -// → at x_mitre_log_sources[0].keys -// ✖ Too small: expected array to have >=1 items -// → at x_mitre_log_sources[0].keys - -/*************************************************************************************************** */ -// Example 11: Valid Analytic with Platform -/*************************************************************************************************** */ -const validAnalyticWithPlatform = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a440", - "name": "Windows Process Creation Detection", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0010", - "url": "https://attack.mitre.org/analytics/AN0010" - } - ], - "x_mitre_platforms": ["Windows"], - "x_mitre_detects": "Windows-specific process creation patterns indicating potential adversary activity" -}; - -console.log("\nExample 11 - Valid Analytic with Platform:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticWithPlatform).name}`) - -/*************************************************************************************************** */ -// Example 12: Invalid Analytic (multiple platforms) -/*************************************************************************************************** */ -const invalidAnalyticMultiplePlatforms = { - ...validAnalytic, - "x_mitre_platforms": ["Windows", "Linux"] -}; - -console.log("\nExample 12 - Invalid Analytic (multiple platforms):"); -try { - analyticSchema.parse(invalidAnalyticMultiplePlatforms); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too big: expected array to have <=1 items -// → at x_mitre_platforms - -/*************************************************************************************************** */ -// Example 13: Valid Mobile Analytic -/*************************************************************************************************** */ -const validMobileAnalytic = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a441", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Mobile Application Permission Abuse", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["mobile-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0020", - "url": "https://attack.mitre.org/analytics/AN0020" - } - ], - "x_mitre_platforms": ["Android"], - "x_mitre_detects": "Mobile applications requesting excessive or suspicious permissions", - "x_mitre_log_sources": [ - { - "ref": `x-mitre-log-source--${uuidv4()}`, - "keys": ["logcat", "system"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "PermissionThreshold", - "description": "Number of permissions that trigger suspicious behavior detection" - } - ] -}; - -console.log("\nExample 13 - Valid Mobile Analytic:"); -console.log(`SUCCESS ${analyticSchema.parse(validMobileAnalytic).name}`) - -/*************************************************************************************************** */ -// Example 14: Invalid Analytic (wrong log source STIX ID format) -/*************************************************************************************************** */ -const invalidAnalyticWrongLogSourceId = { - ...validAnalytic, - "x_mitre_log_sources": [ - { - "ref": "x-mitre-analytic--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["PowerShell"] - } - ] -}; - -console.log("\nExample 14 - Invalid Analytic (wrong log source STIX ID format):"); -try { - analyticSchema.parse(invalidAnalyticWrongLogSourceId); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid STIX Identifier: must start with 'x-mitre-log-source--' -// → at x_mitre_log_sources[0].ref - -/*************************************************************************************************** */ -// Example 15: Valid Analytic with Complex Log Source Keys -/*************************************************************************************************** */ -const validAnalyticComplexKeys = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a442", - "name": "Advanced Process Monitoring", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0030", - "url": "https://attack.mitre.org/analytics/AN0030" - } - ], - "x_mitre_detects": "Complex process creation and execution patterns using multiple log sources", - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["sysmon:1", "auditd:SYSCALL", "Security/Microsoft-Windows-Security-Auditing"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "ProcessPattern", - "description": "Regular expression pattern for matching suspicious process names" - } - ] -}; - -console.log("\nExample 15 - Valid Analytic with Complex Log Source Keys:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticComplexKeys).name}`) - -/*************************************************************************************************** */ -// Example 16: Analytic with unknown property -/*************************************************************************************************** */ -const analyticWithUnknownProperty = { - ...validAnalytic, - foo: 'bar' -} - -console.log("\nExample 16 - Parsing an analytic with an unknown property (foo: 'bar'):"); -try { - const parsedAnalytic = analyticSchema.parse(analyticWithUnknownProperty); - console.log("Parsed successfully. Analytic name:", parsedAnalytic.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Unrecognized key: "foo" - -/*************************************************************************************************** */ -// Example 17: Invalid Analytic (duplicate keys in same log source) -/*************************************************************************************************** */ -const invalidAnalyticDuplicateKeys = { - ...validAnalytic, - "x_mitre_log_sources": [ - { - "ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "keys": ["PowerShell", "PowerShell"] - } - ] -}; - -console.log("\nExample 17 - Invalid Analytic (duplicate keys in same log source):"); -try { - analyticSchema.parse(invalidAnalyticDuplicateKeys); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Duplicate log source permutation found: each (name, channel) pair must be unique -// → at x_mitre_log_sources.x_mitre_log_source_permutations - -/*************************************************************************************************** */ -// Example 18: Invalid Analytic (empty mutable element field) -/*************************************************************************************************** */ -const invalidAnalyticEmptyMutableField = { - ...validAnalytic, - "x_mitre_mutable_elements": [ - { - "field": "", - "description": "Time window for correlation analysis" - } - ] -}; - -console.log("\nExample 18 - Invalid Analytic (empty mutable element field):"); -try { - analyticSchema.parse(invalidAnalyticEmptyMutableField); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected string to have >=1 characters -// → at x_mitre_mutable_elements[0].field \ No newline at end of file diff --git a/examples/sdo/asset.example.ts b/examples/sdo/asset.example.ts deleted file mode 100644 index 32b759f5..00000000 --- a/examples/sdo/asset.example.ts +++ /dev/null @@ -1,229 +0,0 @@ - -import { z } from "zod/v4"; -import { assetSchema } from "../../src/schemas/sdo/asset.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Asset -/*************************************************************************************************** */ -const validAsset = { - "modified": "2023-10-04T18:05:43.237Z", - "name": "Remote Terminal Unit (RTU)", - "description": "A Remote Terminal Unit (RTU) is a device that typically resides between field devices (e.g., PLCs, IEDs) and control/SCADA servers and supports various communication interfacing and data aggregation functions. RTUs are typically responsible for forwarding commands from the control server and the collection of telemetry, events, and alerts from the field devices. An RTU can be implemented as a dedicated embedded device, as software platform that runs on a hardened/ruggedized computer, or using a custom application program on a PLC.", - "x_mitre_platforms": [ - "Embedded", - "Windows", - "Linux" - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "ics-attack" - ], - "x_mitre_version": "1.0", - "type": "x-mitre-asset", - "id": "x-mitre-asset--1769c499-55e5-462f-bab2-c39b8cd5ae32", - "created": "2023-09-28T14:44:54.756Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/assets/A0004", - "external_id": "A0004" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 1: Valid Asset:"); -console.log(`SUCCESS ${assetSchema.parse(validAsset).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Asset (ATT&CK ID does not match format A####) -/*************************************************************************************************** */ -const invalidAssetID = { - ...validAsset, - external_references: [ - { - source_name: "mitre-attack", - external_id: "T1111" - } - ] -}; - -console.log("\nExample 2: Invalid Asset (ATT&CK ID does not match format A####):"); -try { - assetSchema.parse(invalidAssetID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ The first external_reference must match the ATT&CK ID format A####. -// → at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Invalid Asset (missing required fields) -/*************************************************************************************************** */ -const invalidAssetMissingFields = { - "modified": "2023-10-04T18:05:43.237Z", - // Missing name - "description": "A Remote Terminal Unit (RTU) is a device that typically resides between field devices (e.g., PLCs, IEDs) and control/SCADA servers and supports various communication interfacing and data aggregation functions. RTUs are typically responsible for forwarding commands from the control server and the collection of telemetry, events, and alerts from the field devices. An RTU can be implemented as a dedicated embedded device, as software platform that runs on a hardened/ruggedized computer, or using a custom application program on a PLC.", - "x_mitre_sectors": [ - "Electric", - "Water and Wastewater", - "General" - ], - "x_mitre_platforms": [ - "Embedded", - "Windows", - "Linux" - ], - "x_mitre_deprecated": false, - // Missing x_mitre_domains - // Missing x_mitre_version (Defaults to 1.0) - "type": "x-mitre-asset", - "id": "x-mitre-asset--1769c499-55e5-462f-bab2-c39b8cd5ae32", - "created": "2023-09-28T14:44:54.756Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/assets/A0004", - "external_id": "A0004" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 3: Invalid Asset (missing required fields):"); -try { - assetSchema.parse(invalidAssetMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected string, received undefined -// → at name -// ✖ Invalid input: expected string, received undefined -// → at x_mitre_version -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_domains - -/*************************************************************************************************** */ -// Example 4: Asset with invalid type -/*************************************************************************************************** */ -const assetWithInvalidType = { - ...validAsset, - type: "invalid-type" -}; - -console.log("\nExample 4: Asset with invalid type:"); -try { - assetSchema.parse(assetWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected "x-mitre-asset" -// → at type - -/*************************************************************************************************** */ -// Example 5: Asset with optional fields -/*************************************************************************************************** */ -const assetWithOptionalFields = { - ...validAsset, - x_mitre_contributors: ["John Doe", "Jane Smith"], - x_mitre_sectors: [ - "Electric", - "Water and Wastewater", - "General" - ], - x_mitre_related_assets: [ - { - name: "Related Asset", - description: "Description", - related_asset_sectors: ["Electric"] - } - ] -}; - -console.log("\nExample 5: Asset with optional fields:"); -console.log("Parsed successfully:", assetSchema.safeParse(assetWithOptionalFields).success); - -// Parsed successfully: true - -/*************************************************************************************************** */ -// Example 6: Asset with invalid sectors -/*************************************************************************************************** */ -const assetWithInvalidSectors = { - ...validAsset, - x_mitre_sectors: [ - "Invalid Sector" - ] -}; - -console.log("\nExample 6: Asset with invalid sectors:"); -try { - assetSchema.parse(assetWithInvalidSectors); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Sector must be one of: Electric, Water and Wastewater, Manufacturing, Rail, Maritime, General -// → at x_mitre_sectors[0] - -/*************************************************************************************************** */ -// Example 7: Asset with invalid related assets -/*************************************************************************************************** */ -const assetWithInvalidRelatedAssets = { - ...validAsset, - x_mitre_related_assets: [ - { - description: "invalid related asset" - } - ] -}; - -console.log("\nExample 7: Asset with invalid related assets:"); -try { - assetSchema.parse(assetWithInvalidRelatedAssets); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Related asset name is required -// → at x_mitre_related_assets[0].name - -/** ************************************************************************************************* */ -// Example 8: Asset with unknown property -/** ************************************************************************************************* */ -const assetWithUnknownProperty = { - ...validAsset, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing an asset with an unknown property (foo: 'bar'):"); -try { - const parsedAsset = assetSchema.parse(assetWithUnknownProperty); - console.log("Parsed successfully. Asset name:", parsedAsset.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Unrecognized key: "foo" \ No newline at end of file diff --git a/examples/sdo/campaign.example.ts b/examples/sdo/campaign.example.ts deleted file mode 100644 index 13cf1b62..00000000 --- a/examples/sdo/campaign.example.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { z } from "zod/v4"; -import { campaignSchema } from "../../src/schemas/sdo/campaign.schema.js"; -import { stixCreatedByRefSchema, stixCreatedTimestampSchema, stixModifiedTimestampSchema } from "../../src/schemas/common/index.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Campaign -/** ************************************************************************************************* */ -const validCampaign = { - type: "campaign", - id: "campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Operation Dream Job", - x_mitre_version: "1.2", - description: "Operation Dream Job was a cyber espionage operation...", - created_by_ref: stixCreatedByRefSchema.parse("identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5"), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - object_marking_refs: ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/campaigns/C0022", - external_id: "C0022" - }, - { - "source_name": "Operation Interception", - "description": "(Citation: ESET Lazarus Jun 2020)" - }, - { - "source_name": "Operation North Star", - "description": "(Citation: McAfee Lazarus Jul 2020)(Citation: McAfee Lazarus Nov 2020)" - }, - { - "source_name": "McAfee Lazarus Nov 2020", - "description": "Beek, C. (2020, November 5). Operation North Star: Behind The Scenes. Retrieved December 20, 2021.", - "url": "https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-behind-the-scenes/" - }, - { - "source_name": "ESET Lazarus Jun 2020", - "description": "Breitenbacher, D and Osis, K. (2020, June 17). OPERATION IN(TER)CEPTION: Targeted Attacks Against European Aerospace and Military Companies. Retrieved December 20, 2021.", - "url": "https://www.welivesecurity.com/wp-content/uploads/2020/06/ESET_Operation_Interception.pdf" - }, - { - "source_name": "McAfee Lazarus Jul 2020", - "description": "Cashman, M. (2020, July 29). Operation North Star Campaign. Retrieved December 20, 2021.", - "url": "https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-a-job-offer-thats-too-good-to-be-true/?hilite=%27Operation%27%2C%27North%27%2C%27Star%27" - }, - { - "source_name": "ClearSky Lazarus Aug 2020", - "description": "ClearSky Research Team. (2020, August 13). Operation 'Dream Job' Widespread North Korean Espionage Campaign. Retrieved December 20, 2021.", - "url": "https://www.clearskysec.com/wp-content/uploads/2020/08/Dream-Job-Campaign.pdf" - }, - { - "source_name": "The Hacker News Lazarus Aug 2022", - "description": "Lakshmanan, R. (2022, August 17). North Korea Hackers Spotted Targeting Job Seekers with macOS Malware. Retrieved April 10, 2023.", - "url": "https://thehackernews.com/2022/08/north-korea-hackers-spotted-targeting.html" - } - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_deprecated: false, - revoked: false, - aliases: ["Operation Dream Job", "Operation North Star", "Operation Interception"], - first_seen: "2019-09-01T04:00:00.000Z", - last_seen: "2020-08-01T04:00:00.000Z", - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("Example 1 - Valid Campaign:"); -console.log("Parsed successfully:", campaignSchema.safeParse(validCampaign).success); - - -/** ************************************************************************************************* */ -// Example 2: Invalid Campaign (missing required fields) -/** ************************************************************************************************* */ -const invalidCampaign = { - type: "campaign", - id: "campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Invalid Campaign", - x_mitre_version: "1.0", - // Missing description - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2023-03-17T13:37:42.596Z", - modified: "2024-04-11T00:31:21.576Z", - // Missing object_marking_refs - x_mitre_domains: ["enterprise-attack"], - // Missing external_references - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_deprecated: false, - revoked: false, - aliases: [], - first_seen: "2019-09-01T04:00:00.000Z", - last_seen: "2020-08-01T04:00:00.000Z", - // Missing x_mitre_first_seen_citation - // Missing x_mitre_last_seen_citation -}; - -console.log("\nExample 2 - Invalid Campaign (missing required fields):"); -const e2 = campaignSchema.safeParse(invalidCampaign); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -// ✖ Invalid input: expected array, received undefined -// → at external_references -// ✖ Invalid input: expected nonoptional, received undefined -// → at object_marking_refs -// ✖ Invalid input: expected string, received undefined -// → at description -// ✖ Must be one or more citations in the form '(Citation: [citation name])' without any separators -// → at x_mitre_first_seen_citation -// ✖ Must be one or more citations in the form '(Citation: [citation name])' without any separators -// → at x_mitre_last_seen_citation - -/** ************************************************************************************************* */ -// Example 3: Campaign with optional fields -/** ************************************************************************************************* */ -const campaignWithOptionalFields = { - ...validCampaign, - x_mitre_contributors: ["John Doe", "Jane Smith"], -}; - -console.log("\nExample 3 - Campaign with optional fields:"); -console.log("Successfully parsed:", campaignSchema.safeParse(campaignWithOptionalFields).success); - -/** ************************************************************************************************* */ -// Example 4: Campaign with invalid type -/** ************************************************************************************************* */ -const campaignWithInvalidType = { - ...validCampaign, - type: "invalid-type", -}; - -console.log("\nExample 4 - Campaign with invalid type:"); -const e4 = campaignSchema.safeParse(campaignWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -// ✖ Invalid input: expected "campaign" -// → at type - -/** ************************************************************************************************* */ -// Example 5: Campaign with invalid dates -/** ************************************************************************************************* */ -const campaignWithInvalidDates = { - ...validCampaign, - first_seen: "2019-09-01", // Invalid date format - last_seen: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Campaign with invalid dates:"); -try { - campaignSchema.parse(campaignWithInvalidDates); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Invalid STIX timestamp format: must be an RFC3339 timestamp with a timezone specification of 'Z'. -// → at first_seen - -/** ************************************************************************************************* */ -// Example 6: Campaign with invalid citations -/** ************************************************************************************************* */ -const campaignWithInvalidCitations = { - ...validCampaign, - x_mitre_first_seen_citation: "", // Empty string - x_mitre_last_seen_citation: "(Citation: Valid Citation)", -}; - -console.log("\nExample 6 - Campaign with invalid citations:"); -try { - campaignSchema.parse(campaignWithInvalidCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Must be one or more citations in the form '(Citation: [citation name])' without any separators -// → at x_mitre_first_seen_citation - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example campaign -/** ************************************************************************************************* */ -const exampleOfRealCampaign = { - 'type': 'campaign', - 'id': 'campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b', - 'spec_version': '2.1', - 'x_mitre_attack_spec_version': '3.2.0', - 'name': 'Operation Dream Job', - 'x_mitre_version': '1.2', - 'description': '[Operation Dream Job](https://attack.mitre.org/campaigns/C0022) was a cyber espionage operation likely conducted by [Lazarus Group](https://attack.mitre.org/groups/G0032) that targeted the defense, aerospace, government, and other sectors in the United States, Israel, Australia, Russia, and India. In at least one case, the cyber actors tried to monetize their network access to conduct a business email compromise (BEC) operation. In 2020, security researchers noted overlapping TTPs, to include fake job lures and code similarities, between [Operation Dream Job](https://attack.mitre.org/campaigns/C0022), Operation North Star, and Operation Interception; by 2022 security researchers described [Operation Dream Job](https://attack.mitre.org/campaigns/C0022) as an umbrella term covering both Operation Interception and Operation North Star.(Citation: ClearSky Lazarus Aug 2020)(Citation: McAfee Lazarus Jul 2020)(Citation: ESET Lazarus Jun 2020)(Citation: The Hacker News Lazarus Aug 2022)', - 'created_by_ref': 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - 'created': '2023-03-17T13:37:42.596Z', - 'modified': '2024-04-11T00:31:21.576Z', - 'object_marking_refs': ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], - 'x_mitre_domains': ['enterprise-attack'], - 'external_references': [{ - 'source_name': 'mitre-attack', - 'url': 'https://attack.mitre.org/campaigns/C0022', - 'external_id': 'C0022' - }, - { - 'source_name': 'Operation Interception', - 'description': '(Citation: ESET Lazarus Jun 2020)' - }, - { - 'source_name': 'Operation North Star', - 'description': '(Citation: McAfee Lazarus Jul 2020)(Citation: McAfee Lazarus Nov 2020)' - }, - { - 'source_name': 'McAfee Lazarus Nov 2020', - 'description': 'Beek, C. (2020, November 5). Operation North Star: Behind The Scenes. Retrieved December 20, 2021.', - 'url': 'https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-behind-the-scenes/' - }, - { - 'source_name': 'ESET Lazarus Jun 2020', - 'description': 'Breitenbacher, D and Osis, K. (2020, June 17). OPERATION IN(TER)CEPTION: Targeted Attacks Against European Aerospace and Military Companies. Retrieved December 20, 2021.', - 'url': 'https://www.welivesecurity.com/wp-content/uploads/2020/06/ESET_Operation_Interception.pdf' - }, - { - 'source_name': 'McAfee Lazarus Jul 2020', - 'description': 'Cashman, M. (2020, July 29). Operation North Star Campaign. Retrieved December 20, 2021.', - 'url': 'https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-a-job-offer-thats-too-good-to-be-true/?hilite=%27Operation%27%2C%27North%27%2C%27Star%27' - }, - { - 'source_name': 'ClearSky Lazarus Aug 2020', - 'description': "ClearSky Research Team. (2020, August 13). Operation 'Dream Job' Widespread North Korean Espionage Campaign. Retrieved December 20, 2021.", - 'url': 'https://www.clearskysec.com/wp-content/uploads/2020/08/Dream-Job-Campaign.pdf' - }, - { - 'source_name': 'The Hacker News Lazarus Aug 2022', - 'description': 'Lakshmanan, R. (2022, August 17). North Korea Hackers Spotted Targeting Job Seekers with macOS Malware. Retrieved April 10, 2023.', - 'url': 'https://thehackernews.com/2022/08/north-korea-hackers-spotted-targeting.html' - }], - 'x_mitre_modified_by_ref': 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - 'x_mitre_deprecated': false, - 'revoked': false, - 'aliases': ['Operation Dream Job', - 'Operation North Star', - 'Operation Interception'], - 'first_seen': '2019-09-01T04:00:00.000Z', - 'last_seen': '2020-08-01T04:00:00.000Z', - 'x_mitre_first_seen_citation': '(Citation: ESET Lazarus Jun 2020)', - 'x_mitre_last_seen_citation': '(Citation: ClearSky Lazarus Aug 2020)' -} - -console.log("\nExample 7 - Parsing the provided example campaign:"); -const e7 = campaignSchema.safeParse(exampleOfRealCampaign); -console.log("Successfully parsed:", e7.success); - -// Successfully parsed: true - -/** ************************************************************************************************* */ -// Example 8: Campaign with unknown property -/** ************************************************************************************************* */ -const campaignWithUnknownProperty = { - ...exampleOfRealCampaign, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a campaign with an unknown property (foo: 'bar'):"); -try { - const parsedCampaign = campaignSchema.parse(campaignWithUnknownProperty); - console.log("Parsed successfully. Campaign name:", parsedCampaign.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Unrecognized key: "foo" - -/** ************************************************************************************************* */ -// Example 9: Campaign with multiple valid citations -/** ************************************************************************************************* */ -const campaignWithMultipleCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: McAfee Lazarus Jul 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: The Hacker News Lazarus Aug 2022)" -}; - -console.log("\nExample 9 - Campaign with multiple valid citations:"); -const e9 = campaignSchema.safeParse(campaignWithMultipleCitations); -console.log("Successfully parsed:", e9.success); - -// Successfully parsed: true - -/** ************************************************************************************************* */ -// Example 10: Campaign with invalid multiple citations (missing parentheses) -/** ************************************************************************************************* */ -const campaignWithInvalidMultipleCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020) (Citation: McAfee Lazarus Jul 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: Invalid Citation)" -}; - -console.log("\nExample 10 - Campaign with invalid multiple citations:"); -try { - campaignSchema.parse(campaignWithInvalidMultipleCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Must be one or more citations in the form '(Citation: [citation name])' without any separators -// → at x_mitre_first_seen_citation - -/** ************************************************************************************************* */ -// Example 11: Campaign with citation not in external_references -/** ************************************************************************************************* */ -const campaignWithNonExistentCitation = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: Non-existent Source)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("\nExample 11 - Campaign with citation not in external_references:"); -try { - campaignSchema.parse(campaignWithNonExistentCitation); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Citation Non-existent Source not found in external_references. -// → at x_mitre_first_seen_citation[1] - -/** ************************************************************************************************* */ -// Example 12: Campaign with mixed valid and invalid citations -/** ************************************************************************************************* */ -const campaignWithMixedCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: Invalid Citation)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: The Hacker News Lazarus Aug 2022)" -}; - -console.log("\nExample 12 - Campaign with mixed valid and invalid citations:"); -try { - campaignSchema.parse(campaignWithMixedCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Citation Invalid Citation not found in external_references. -// → at x_mitre_first_seen_citation[1] - -/** ************************************************************************************************* */ -// Example 13: Campaign with empty citation string -/** ************************************************************************************************* */ -const campaignWithEmptyCitation = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("\nExample 13 - Campaign with empty citation string:"); -try { - campaignSchema.parse(campaignWithEmptyCitation); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// ✖ Must be one or more citations in the form '(Citation: [citation name])' without any separators -// → at x_mitre_first_seen_citation \ No newline at end of file diff --git a/examples/sdo/collection.example.ts b/examples/sdo/collection.example.ts deleted file mode 100644 index 29d63c99..00000000 --- a/examples/sdo/collection.example.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { z } from "zod/v4"; -import { collectionSchema } from "../../src/schemas/sdo/collection.schema.js"; - -/****************************************************************************************************/ -// Example 1: Valid Collection -/****************************************************************************************************/ -const validCollection = { - "id": "x-mitre-collection--1f5f1533-f617-4ca8-9ab4-6a02367fa019", - "type": "x-mitre-collection", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "name": "Enterprise ATT&CK", - "x_mitre_version": "6.2", - "description": "Version 6.2 of the Enterprise ATT&CK dataset", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2018-10-17T00:14:20.652Z", - "modified": "2019-10-11T19:30:42.406Z", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_contents": [ - { - "object_ref": "attack-pattern--01a5a209-b94c-450b-b7f9-946497d91055", - "object_modified": "2019-07-17T20:04:40.297Z" - }, - { - "object_ref": "attack-pattern--0259baeb-9f63-4c69-bf10-eb038c390688", - "object_modified": "2019-06-18T13:58:28.377Z" - }, - { - "object_ref": "relationship--0024d82d-97ea-4dc5-81a1-8738862e1f3b", - "object_modified": "2019-04-24T23:59:16.298Z" - }, - { - "object_ref": "intrusion-set--090242d7-73fc-4738-af68-20162f7a5aae", - "object_modified": "2019-03-22T14:21:19.419Z" - }, - { - "object_ref": "malware--069af411-9b24-4e85-b26c-623d035bbe84", - "object_modified": "2019-04-22T22:40:40.953Z" - }, - { - "object_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_modified": "2017-06-01T00:00:00.000Z" - }, - { - "object_ref": "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - "object_modified": "2017-06-01T00:00:00.000Z" - } - ] -} - -console.log("\nExample 1: Valid Collection:"); -console.log(`SUCCESS ${collectionSchema.parse(validCollection).name}`) - -/****************************************************************************************************/ -// Example 2: Invalid Collection (missing required fields) -/****************************************************************************************************/ -const invalidCollectionMissingFields = { - "id": "x-mitre-collection--1f5f1533-f617-4ca8-9ab4-6a02367fa019", - "type": "x-mitre-collection", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_version": "6.2", - "description": "Version 6.2 of the Enterprise ATT&CK dataset", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2018-10-17T00:14:20.652Z", - "modified": "2019-10-11T19:30:42.406Z", -} -console.log("\nExample 2: Invalid Collection (missing required fields):"); -const e2 = collectionSchema.safeParse(invalidCollectionMissingFields); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 3: Collection with invalid type -/****************************************************************************************************/ -const collectionWithInvalidType = { - ...validCollection, - type: "invalid-type" -}; - -console.log("\nExample 3: Collection with invalid type:"); -const e3 = collectionSchema.safeParse(collectionWithInvalidType); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 4: Collection with optional fields -/****************************************************************************************************/ -const collectionWithOptionalFields = { - ...validCollection, - description: "Version 6.2 of the Enterprise ATT&CK dataset" -}; - -console.log("\nExample 4: Collection with optional fields:"); -console.log(collectionSchema.parse(collectionWithOptionalFields).description); - -/****************************************************************************************************/ -// Example 5: Collection with invalid object version references -/****************************************************************************************************/ -const collectionWithInvalidContents = { - ...validCollection, - x_mitre_contents: [ - { - object_ref: "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - }, - { - object_modified: "2024-02-02T19:04:35.389Z" - }, - { - object_ref: "invalid-id", - object_modified: "2024-02-02T19:04:35.389Z" - } - ] -}; - -console.log("\nExample 5: Collection with invalid object version references:"); -const e5 = collectionSchema.safeParse(collectionWithInvalidContents); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Collection with unknown property -/** ************************************************************************************************* */ -const collectionWithUnknownProperty = { - ...validCollection, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a collection with an unknown property (foo: 'bar'):"); -const e6 = collectionSchema.safeParse(collectionWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Collection name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/data-component.example.ts b/examples/sdo/data-component.example.ts deleted file mode 100644 index 368c118f..00000000 --- a/examples/sdo/data-component.example.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { z } from "zod/v4"; -import { dataComponentSchema } from "../../src/schemas/sdo/data-component.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Data Component -/** ************************************************************************************************* */ -const validDataComponent = { - modified: "2022-10-20T20:18:06.745Z", - name: "Network Connection Creation", - description: - "Initial construction of a network connection, such as capturing socket information with a source/destination IP and port(s) (ex: Windows EID 5156, Sysmon EID 3, or Zeek conn.log)", - x_mitre_data_source_ref: - "x-mitre-data-source--c000cd5c-bbb3-4606-af6f-6c6d9de0bbe3", - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--181a9f8c-c780-4f1f-91a8-edb770e904ba", - created: "2021-10-20T15:05:19.274Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("Example 1 - Valid Data Component:"); -console.log(dataComponentSchema.parse(validDataComponent)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Data Component (missing required fields) -/** ************************************************************************************************* */ -const invalidDataComponent = { - modified: "2022-10-20T20:18:06.745Z", - name: "Network Connection Creation", - x_mitre_data_source_ref: - "x-mitre-data-source--c000cd5c-bbb3-4606-af6f-6c6d9de0bbe3", - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--181a9f8c-c780-4f1f-91a8-edb770e904ba", - created: "2021-10-20T15:05:19.274Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 2 - Invalid Data Component (missing required fields):"); -const e2 = dataComponentSchema.safeParse(invalidDataComponent); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Data Component with optional fields -/** ************************************************************************************************* */ -const dataComponentWithOptionalFields = { - ...validDataComponent, - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Data Component with optional fields:"); -console.log(dataComponentSchema.parse(dataComponentWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Data Component with invalid type -/** ************************************************************************************************* */ -const dataComponentWithInvalidType = { - ...validDataComponent, - type: "invalid-type", -}; - -console.log("\nExample 4 - Data Component with invalid type:"); -const e4 = dataComponentSchema.safeParse(dataComponentWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Data Component with invalid dates -/** ************************************************************************************************* */ -const dataComponentWithInvalidDates = { - ...validDataComponent, - created: "2019-09-01", - modified: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Data Component with invalid dates:"); -const e5 = dataComponentSchema.safeParse(dataComponentWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Data Component -/** ************************************************************************************************* */ -const exampleOfRealDataComponent = { - modified: "2022-10-07T16:15:56.932Z", - name: "Process Creation", - description: - "The initial construction of an executable managed by the OS, that may involve one or more tasks or threads. (e.g. Win EID 4688, Sysmon EID 1, cmd.exe > net use, etc.)", - x_mitre_data_source_ref: - "x-mitre-data-source--e8b8ede7-337b-4c0c-8c32-5c7872c1ee22", - x_mitre_deprecated: false, - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--3d20385b-24ef-40e1-9f56-f39750379077", - created: "2021-10-20T15:05:19.272Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 6 - Parsing the provided example data component:"); -const e6 = dataComponentSchema.safeParse(exampleOfRealDataComponent); -if (e6.success) { - console.log("Parsed successfully. Data Component name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: DataComponent with unknown property -/** ************************************************************************************************* */ -const dataComponentWithUnknownProperty = { - ...exampleOfRealDataComponent, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a dataComponent with an unknown property (foo: 'bar'):"); -const e7 = dataComponentSchema.safeParse(dataComponentWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. DataComponent name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/data-source.example.ts b/examples/sdo/data-source.example.ts deleted file mode 100644 index df5a072f..00000000 --- a/examples/sdo/data-source.example.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { z } from "zod/v4"; -import { - stixCreatedByRefSchema, - stixCreatedTimestampSchema, - stixModifiedTimestampSchema, -} from "../../src/schemas/common/index.js"; -import { dataSourceSchema } from "../../src/schemas/sdo/data-source.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Data Source -/** ************************************************************************************************* */ -const validDataSource = { - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Test Data Source", - x_mitre_version: "1.2", - description: "Test data source description", - created_by_ref: stixCreatedByRefSchema.parse( - "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" - ), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/datasources/DS0014", - external_id: "DS0014" - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_collection_layers: ["Host"], -}; - -console.log("Example 1 - Valid Data Source:"); -console.log(dataSourceSchema.parse(validDataSource)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Data Source (missing required fields) -/** ************************************************************************************************* */ -const invalidDataSource = { - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Test Data Source", - x_mitre_version: "1.2", - created_by_ref: stixCreatedByRefSchema.parse( - "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" - ), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - x_mitre_platforms: ["Windows", "Linux"], - x_mitre_domains: ["enterprise-attack"], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_contributors: ["Test Contributer"], - x_mitre_deprecated: false, - revoked: false, - x_mitre_collection_layers: ["Host"], -}; - -console.log("\nExample 2 - Invalid Data Source (missing required fields):"); -const e2 = dataSourceSchema.safeParse(invalidDataSource); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Data Source with optional fields -/** ************************************************************************************************* */ -const dataSourceWithOptionalFields = { - ...validDataSource, - x_mitre_platforms: ['Windows', 'Linux'], - x_mitre_contributors: ["John Doe", "Jane Smith"], - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Data Source with optional fields:"); -console.log(dataSourceSchema.parse(dataSourceWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Data Source with invalid type -/** ************************************************************************************************* */ -const dataSourceWithInvalidType = { - ...validDataSource, - type: "invalid-type", -}; - -console.log("\nExample 4 - Data Source with invalid type:"); -const e4 = dataSourceSchema.safeParse(dataSourceWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Data Source with invalid dates -/** ************************************************************************************************* */ -const dataSourceWithInvalidDates = { - ...validDataSource, - created: "2019-09-01", - modified: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Data source with invalid dates:"); -const e5 = dataSourceSchema.safeParse(dataSourceWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Data Source -/** ************************************************************************************************* */ -const exampleOfRealDataSource = { - modified: "2023-04-20T18:38:40.409Z", - name: "Sensor Health", - description: - "Information from host telemetry providing insights about system status, errors, or other notable functional activity", - x_mitre_platforms: ["Linux", "Windows", "macOS", "Android", "iOS"], - x_mitre_deprecated: false, - x_mitre_domains: ["enterprise-attack", "mobile-attack"], - x_mitre_version: "1.1", - x_mitre_contributors: ["Center for Threat-Informed Defense (CTID)"], - x_mitre_collection_layers: ["Host"], - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - created: "2021-10-20T15:05:19.272Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/datasources/DS0013", - external_id: "DS0013", - }, - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "3.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", -}; - -console.log("\nExample 6 - Parsing the provided example data source:"); -const e6 = dataSourceSchema.safeParse(exampleOfRealDataSource); -if (e6.success) { - console.log("Parsed successfully. Data Source name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: DataSource with unknown property -/** ************************************************************************************************* */ -const dataSourceWithUnknownProperty = { - ...exampleOfRealDataSource, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a dataSource with an unknown property (foo: 'bar'):"); -const e7 = dataSourceSchema.safeParse(dataSourceWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. DataSource name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/detection-strategy.example.ts b/examples/sdo/detection-strategy.example.ts deleted file mode 100644 index aedc6eda..00000000 --- a/examples/sdo/detection-strategy.example.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import { detectionStrategySchema } from "../../src/schemas/sdo/detection-strategy.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Detection Strategy -/*************************************************************************************************** */ -const validDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43d", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Credential Dumping via Sensitive Memory and Registry Access Correlation", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0001", - "url": "https://attack.mitre.org/detection-strategies/DET0001" - } - ], - "x_mitre_contributors": [ - "Security Research Team", - "John Smith" - ], - "x_mitre_analytics": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}` - ] -}; - -console.log("\nExample 1 - Valid Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Detection Strategy (ATT&CK ID does not match format DET####) -/*************************************************************************************************** */ -const invalidDetectionStrategyID = { - ...validDetectionStrategy, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/detection-strategies/DET0001", - "external_id": "D001" - } - ], -}; - -console.log("\nExample 2 - Invalid Detection Strategy (ATT&CK ID does not match format DET####):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: ✖ The first external_reference must match the ATT&CK ID format DET####. -// → at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Valid Detection Strategy with Multiple Contributors -/*************************************************************************************************** */ -const validDetectionStrategyWithContributors = { - ...validDetectionStrategy, - "name": "PowerShell Obfuscation Detection", - "x_mitre_contributors": [ - "John Smith", - "Jane Doe", - "Security Research Team", - "Advanced Threat Analytics Lab" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0002", - "url": "https://attack.mitre.org/detection-strategies/DET0002" - } - ], - "x_mitre_analytics": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 3 - Valid Detection Strategy with Multiple Contributors:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategyWithContributors).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Detection Strategy (missing required fields) -/*************************************************************************************************** */ -const invalidDetectionStrategyMissingFields = { - "id": "x-mitre-detection-strategy--550e8400-e29b-41d4-a716-446655440001", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - // Missing name - // Missing created_by_ref (required by schema) - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // Missing object_marking_refs (required by schema) - // Missing x_mitre_domains - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0003", - "url": "https://attack.mitre.org/detection-strategies/DET0003" - } - ], - // Missing x_mitre_contributors (required) - // Missing x_mitre_analytics -}; - -console.log("\nExample 4 - Invalid Detection Strategy (missing required fields):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: ✖ Invalid input: expected nonoptional, received undefined -// → at created_by_ref -// ✖ Invalid input: expected nonoptional, received undefined -// → at object_marking_refs -// ✖ Invalid input: expected string, received undefined -// → at name -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_contributors -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_analytics -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_domains - -/*************************************************************************************************** */ -// Example 5: Invalid Detection Strategy with invalid type -/*************************************************************************************************** */ -const detectionStrategyWithInvalidType = { - ...validDetectionStrategy, - "type": 'invalid-type' -} - -console.log("\nExample 5 - Detection Strategy with invalid type:"); -try { - detectionStrategySchema.parse(detectionStrategyWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected "x-mitre-detection-strategy" -// → at type - -/*************************************************************************************************** */ -// Example 6: Invalid Detection Strategy (empty analytics array) -/*************************************************************************************************** */ -const invalidDetectionStrategyEmptyAnalytics = { - ...validDetectionStrategy, - "x_mitre_analytics": [] -}; - -console.log("\nExample 6 - Invalid Detection Strategy (empty analytics array):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyEmptyAnalytics); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: ✖ Too small: expected array to have >=1 items -// → at x_mitre_analytics - -/*************************************************************************************************** */ -// Example 7: Invalid Detection Strategy (wrong analytic ID format) -/*************************************************************************************************** */ -const invalidDetectionStrategyAnalyticID = { - ...validDetectionStrategy, - "x_mitre_analytics": [ - "invalid-analytic-id", - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 7 - Invalid Detection Strategy (wrong analytic ID format):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyAnalyticID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: ✖ Invalid STIX Identifier: must comply with format 'type--UUIDv4' -// → at x_mitre_analytics[0] -// ✖ Invalid STIX Identifier for STIX object: contains invalid STIX type 'invalid-analytic-id' -// → at x_mitre_analytics[0] -// ✖ Invalid STIX Identifier for STIX object: contains invalid UUIDv4 format -// → at x_mitre_analytics[0] -// ✖ Invalid STIX Identifier: must start with 'x-mitre-analytic--' -// → at x_mitre_analytics[0] - -/*************************************************************************************************** */ -// Example 8: Invalid Detection Strategy (wrong STIX type in analytic ID) -/*************************************************************************************************** */ -const invalidDetectionStrategyWrongStixType = { - ...validDetectionStrategy, - "x_mitre_analytics": [ - "x-mitre-detection-strategy--6ba7b810-9dad-11d1-80b4-00c04fd430c8" - ] -}; - -console.log("\nExample 8 - Invalid Detection Strategy (wrong STIX type in analytic ID):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyWrongStixType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: ✖ Invalid STIX Identifier: must start with 'x-mitre-analytic--' -// → at x_mitre_analytics[0] - -/*************************************************************************************************** */ -// Example 9: Valid Detection Strategy with Mobile Domain -/*************************************************************************************************** */ -const validMobileDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c8d9e10-f3ab-22e5-b827-556766550001", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Mobile Application Behavior Analysis", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.2", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": [ - "mobile-attack" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0015", - "url": "https://attack.mitre.org/detection-strategies/DET0015" - } - ], - "x_mitre_contributors": [ - "Mobile Security Research Lab", - "Dr. Sarah Johnson" - ], - "x_mitre_analytics": [ - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 9 - Valid Mobile Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validMobileDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 10: Valid Multi-Domain Detection Strategy -/*************************************************************************************************** */ -const validMultiDomainDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43e", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Cross-Platform Command Execution Detection", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": [ - "enterprise-attack", - "ics-attack" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0025", - "url": "https://attack.mitre.org/detection-strategies/DET0025" - } - ], - "x_mitre_contributors": [ - "Cross-Platform Security Team", - "Industrial Control Systems Lab" - ], - "x_mitre_analytics": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 10 - Valid Multi-Domain Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validMultiDomainDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 11: Invalid Detection Strategy (invalid domain) -/*************************************************************************************************** */ -const invalidDetectionStrategyDomain = { - ...validDetectionStrategy, - "x_mitre_domains": [ - "invalid-domain" - ] -}; - -console.log("\nExample 11 - Invalid Detection Strategy (invalid domain):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyDomain); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: ✖ Invalid option: expected one of "enterprise-attack"|"mobile-attack"|"ics-attack" -// → at x_mitre_domains[0] - -/*************************************************************************************************** */ -// Example 12: Invalid Detection Strategy (missing contributors) -/*************************************************************************************************** */ -const invalidDetectionStrategyMissingContributors = { - ...validDetectionStrategy, - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0030", - "url": "https://attack.mitre.org/detection-strategies/DET0030" - } - ] -}; -// Remove required contributors field -delete invalidDetectionStrategyMissingContributors.x_mitre_contributors; - -console.log("\nExample 12 - Invalid Detection Strategy (missing contributors):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyMissingContributors); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: ✖ Invalid input: expected array, received undefined -// → at x_mitre_contributors - -/*************************************************************************************************** */ -// Example 13: Valid Detection Strategy with Special Characters -/*************************************************************************************************** */ -const validDetectionStrategySpecialChars = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43f", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Advanced Persistent Threat (APT) Detection Strategy v2.0", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0099", - "url": "https://attack.mitre.org/detection-strategies/DET0099" - } - ], - "x_mitre_contributors": [ - "Dr. John Smith, PhD", - "Jane Doe (Lead Analyst)", - "Security Team Alpha-1", - "José García-Rodriguez", - "李小明", - "user@security-domain.com" - ], - "x_mitre_analytics": [ - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 13 - Valid Detection Strategy with Special Characters:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategySpecialChars).name}`) - -/*************************************************************************************************** */ -// Example 14: Detection Strategy with unknown property -/*************************************************************************************************** */ -const detectionStrategyWithUnknownProperty = { - ...validDetectionStrategy, - foo: 'bar' -} - -console.log("\nExample 14 - Parsing a detection strategy with an unknown property (foo: 'bar'):"); -try { - const parsedDetectionStrategy = detectionStrategySchema.parse(detectionStrategyWithUnknownProperty); - console.log("Parsed successfully. Detection Strategy name:", parsedDetectionStrategy.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: ✖ Unrecognized key: "foo" - -/*************************************************************************************************** */ -// Example 15: Valid Detection Strategy with Large Number of Analytics -/*************************************************************************************************** */ -const validDetectionStrategyManyAnalytics = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a440", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Comprehensive Threat Detection Suite", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0100", - "url": "https://attack.mitre.org/detection-strategies/DET0100" - } - ], - "x_mitre_contributors": [ - "Comprehensive Detection Team" - ], - "x_mitre_analytics": Array.from({ length: 20 }, (_, i) => - `x-mitre-analytic--${uuidv4()}`, - ) -}; - -console.log("\nExample 15 - Valid Detection Strategy with Many Analytics:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategyManyAnalytics).name} (${validDetectionStrategyManyAnalytics.x_mitre_analytics.length} analytics)`); \ No newline at end of file diff --git a/examples/sdo/group.example.ts b/examples/sdo/group.example.ts deleted file mode 100644 index 1d2cb190..00000000 --- a/examples/sdo/group.example.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import type { StixCreatedTimestamp, StixModifiedTimestamp } from "../../src/schemas/common/index.js"; -import { groupSchema } from "../../src/schemas/sdo/group.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Group -/** ************************************************************************************************* */ -const validGroup = { - id: `intrusion-set--${uuidv4()}`, - type: "intrusion-set", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Test Name", - x_mitre_version: "1.0", - created: "2017-06-01T00:00:00.000Z" as StixCreatedTimestamp, - modified: "2017-06-01T00:00:00.000Z" as StixModifiedTimestamp, - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - external_id: "G1000", - url: "https://attack.mitre.org/groups/G1000", - }, - { - source_name: "Dragos", - url: "https://dragos.com/resource/allanite/", - description: "Dragos Allanite Retrieved. 2019/10/27 ", - }, - ], -}; - -console.log("Example 1 - Valid Group:"); -console.log(groupSchema.parse(validGroup)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Group (missing required fields) -/** ************************************************************************************************* */ -const invalidGroup = { - modified: "2024-04-17T16:13:43.697Z", - x_mitre_version: "1.4", - type: "intrusion-set", - id: "intrusion-set--9538b1a4-4120-4e2d-bf59-3b11fcab05a4", - created: "2019-04-16T15:14:38.533Z", - x_mitre_attack_spec_version: "3.2.0", - spec_version: "2.1", -}; - -console.log("\nExample 2 - Invalid Group (missing required fields):"); -const e2 = groupSchema.safeParse(invalidGroup); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Group with optional fields -/** ************************************************************************************************* */ -const groupWithOptionalFields = { - ...validGroup, - description: - "[TEMP.Veles](https://attack.mitre.org/groups/G0088) is a Russia-based threat group that has targeted critical infrastructure. The group has been observed utilizing [TRITON](https://attack.mitre.org/software/S0609), a malware framework designed to manipulate industrial safety systems.(Citation: FireEye TRITON 2019)(Citation: FireEye TEMP.Veles 2018)(Citation: FireEye TEMP.Veles JSON April 2019)", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/groups/G0088", - external_id: "G0088", - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_contributors: ["Dragos Threat Intelligence"], - x_mitre_deprecated: false, - revoked: false, - aliases: ["Test Name", "XENOTIME"], -}; - -console.log("\nExample 3 - Group with optional fields:"); -console.log(groupSchema.parse(groupWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Group with invalid type -/** ************************************************************************************************* */ -const grouptWithInvalidType = { - ...validGroup, - type: "invalid-type", -}; - -console.log("\nExample 4 - Group with invalid type:"); -const e4 = groupSchema.safeParse(grouptWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Group with invalid dates -/** ************************************************************************************************* */ -const groupWithInvalidDates = { - ...validGroup, - first_seen: "2019-09-01", - last_seen: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Group with invalid dates:"); -const e5 = groupSchema.safeParse(groupWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Group -/** ************************************************************************************************* */ -const exampleOfRealGroup = { - modified: "2024-04-10T18:39:36.997Z", - name: "CyberAv3ngers", - description: - "The [CyberAv3ngers](https://attack.mitre.org/groups/G1027) are a suspected Iranian Government Islamic Revolutionary Guard Corps (IRGC)-affiliated APT group. The [CyberAv3ngers](https://attack.mitre.org/groups/G1027) have been known to be active since at least 2020, with disputed and false claims of critical infrastructure compromises in Israel.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)\n\nIn 2023, the [CyberAv3ngers](https://attack.mitre.org/groups/G1027) engaged in a global targeting and hacking of the Unitronics [Programmable Logic Controller (PLC)](https://attack.mitre.org/assets/A0003) with [Human-Machine Interface (HMI)](https://attack.mitre.org/assets/A0002). This PLC can be found in multiple sectors, including water and wastewater, energy, food and beverage manufacturing, and healthcare. The most notable feature of this attack was the defacement of the devices user interface.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)", - aliases: ["CyberAv3ngers", "Soldiers of Soloman"], - x_mitre_deprecated: false, - x_mitre_version: "1.0", - type: "intrusion-set", - id: "intrusion-set--a07a367a-146c-45a8-a830-d3d337b9befa", - created: "2024-03-25T19:57:07.829Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/groups/G1027", - external_id: "G1027", - }, - { - source_name: "Soldiers of Soloman", - description: - "CyberAv3ngers reportedly has connections to the IRGC-linked group Soldiers of Solomon.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)", - }, - { - source_name: "CISA AA23-335A IRGC-Affiliated December 2023", - description: - "DHS/CISA. (2023, December 1). IRGC-Affiliated Cyber Actors Exploit PLCs in Multiple Sectors, Including U.S. Water and Wastewater Systems Facilities. Retrieved March 25, 2024.", - url: "https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-335a", - }, - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["ics-attack"], - x_mitre_attack_spec_version: "3.2.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", -}; - -console.log("\nExample 6 - Parsing the provided example group:"); -const e6 = groupSchema.safeParse(exampleOfRealGroup); -if (e6.success) { - console.log("Parsed successfully. Group name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: Group with unknown property -/** ************************************************************************************************* */ -const groupWithUnknownProperty = { - ...validGroup, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a group with an unknown property (foo: 'bar'):"); -const e7 = groupSchema.safeParse(groupWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. Group name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/identity.example.ts b/examples/sdo/identity.example.ts deleted file mode 100644 index 85d37dc0..00000000 --- a/examples/sdo/identity.example.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { z } from "zod/v4" -import { identitySchema } from "../../src/schemas/sdo/identity.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Identity -/** ************************************************************************************************* */ - -const validIdentity = { - type: 'identity', - id: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - created: "2017-06-01T00:00:00.000Z", - modified: "2017-06-01T00:00:00.000Z", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - identity_class: "organization", - name: "The MITRE Corporation", - x_mitre_attack_spec_version: "3.2.0", - spec_version: "2.1" -}; - -console.log("Example 1 - Valid Identity:"); -console.log(identitySchema.parse(validIdentity)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Identity (missing required fields) -/** ************************************************************************************************* */ - -const invalidIdentity = { - type: 'identity', - id: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - created: "2017-06-01T00:00:00.000Z", - modified: "2017-06-01T00:00:00.000Z", - x_mitre_domains:["enterprise-attack"], - name: "The MITRE Corporation", - x_mitre_attack_spec_version: "3.2.0", - x_mitre_version: "1.0", - spec_version: "2.1" -}; - -console.log("Example 2 - Invalid Identity (missing required fields):"); -const e2 = identitySchema.safeParse(invalidIdentity); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Identity with invalid id -/** ************************************************************************************************* */ -const identityWithInvalidId = { - ...validIdentity, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 3 - Identity with invalid id:"); -const e3 = identitySchema.safeParse(identityWithInvalidId); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 4: Identity with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const identityWithStixFields = { - ...validIdentity, - description: "identity object description", - roles: ["administrator"], - sectors: ["non-profit"], - contact_information: "attack@mitre.org" -}; - -console.log("\nExample 4 - Identity with fields in STIX but not in ATT&CK:"); -console.log(identitySchema.parse(identityWithStixFields)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example identity -/** ************************************************************************************************* */ -const exampleOfRealIdentity = { - "type": "identity", - "id": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-06-01T00:00:00.000Z", - "modified": "2017-06-01T00:00:00.000Z", - "name": "The MITRE Corporation", - "identity_class": "organization", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_version": "1.0", - "spec_version": "2.1", - "x_mitre_domains":["enterprise-attack"], -} - -console.log("\nExample 5 - Parsing the provided example identity:"); -const e5 = identitySchema.safeParse(exampleOfRealIdentity); -if (e5.success) { - console.log(e5.data); - console.log("Parsed successfully. Identity name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Identity with unknown property -/** ************************************************************************************************* */ -const identityWithUnknownProperty = { - ...exampleOfRealIdentity, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a identity with an unknown property (foo: 'bar'):"); -const e6 = identitySchema.safeParse(identityWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Identity name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/log-source.example.ts b/examples/sdo/log-source.example.ts deleted file mode 100644 index c0c51601..00000000 --- a/examples/sdo/log-source.example.ts +++ /dev/null @@ -1,523 +0,0 @@ -import { z } from "zod/v4"; -import { logSourceSchema } from "../../src/schemas/sdo/log-source.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Log Source -/*************************************************************************************************** */ -const validLogSource = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a43d", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Process Access", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0001", - "url": "https://attack.mitre.org/log-sources/LS0001" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "sysmon", - "channel": "EventCode=10" - }, - { - "name": "auditd:SYSCALL", - "channel": "ptrace" - } - ] -}; - -console.log("\nExample 1 - Valid Log Source:"); -console.log(`SUCCESS ${logSourceSchema.parse(validLogSource).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Log Source (ATT&CK ID does not match format LS####) -/*************************************************************************************************** */ -const invalidLogSourceID = { - ...validLogSource, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/log-sources/LS0001", - "external_id": "L001" - } - ], -}; - -console.log("\nExample 2 - Invalid Log Source (ATT&CK ID does not match format LS####):"); -try { - logSourceSchema.parse(invalidLogSourceID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ The first external_reference must match the ATT&CK ID format LS####. -// → at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Valid Log Source with Multiple Permutations -/*************************************************************************************************** */ -const validLogSourceMultiplePermutations = { - ...validLogSource, - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a43e", - "name": "Windows Security Event Log", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0002", - "url": "https://attack.mitre.org/log-sources/LS0002" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "Security", - "channel": "Security" - }, - { - "name": "System", - "channel": "System" - }, - { - "name": "Application", - "channel": "Application" - } - ] -}; - -console.log("\nExample 3 - Valid Log Source with Multiple Permutations:"); -console.log(`SUCCESS ${logSourceSchema.parse(validLogSourceMultiplePermutations).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Log Source (missing required fields) -/*************************************************************************************************** */ -const invalidLogSourceMissingFields = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a43f", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - // Missing name - // Missing created_by_ref (required by schema) - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // Missing object_marking_refs (required by schema) - // Missing x_mitre_domains - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0003", - "url": "https://attack.mitre.org/log-sources/LS0003" - } - ], - // Missing x_mitre_log_source_permutations -}; - -console.log("\nExample 4 - Invalid Log Source (missing required fields):"); -try { - logSourceSchema.parse(invalidLogSourceMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected nonoptional, received undefined -// → at created_by_ref -// ✖ Invalid input: expected nonoptional, received undefined -// → at object_marking_refs -// ✖ Invalid input: expected string, received undefined -// → at name -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_domains -// ✖ Invalid input: expected array, received undefined -// → at x_mitre_log_source_permutations - -/*************************************************************************************************** */ -// Example 5: Invalid Log Source with invalid type -/*************************************************************************************************** */ -const logSourceWithInvalidType = { - ...validLogSource, - "type": 'invalid-type' -} - -console.log("\nExample 5 - Log Source with invalid type:"); -try { - logSourceSchema.parse(logSourceWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Invalid input: expected "x-mitre-log-source" -// → at type - -/*************************************************************************************************** */ -// Example 6: Invalid Log Source (empty permutations array) -/*************************************************************************************************** */ -const invalidLogSourceEmptyPermutations = { - ...validLogSource, - "x_mitre_log_source_permutations": [] -}; - -console.log("\nExample 6 - Invalid Log Source (empty permutations array):"); -try { - logSourceSchema.parse(invalidLogSourceEmptyPermutations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// Validation errors: ✖ Array must contain at least 1 element(s) -// → at x_mitre_log_source_permutations - -/*************************************************************************************************** */ -// Example 7: Invalid Log Source (duplicate permutations) -/*************************************************************************************************** */ -const invalidLogSourceDuplicatePermutations = { - ...validLogSource, - "x_mitre_log_source_permutations": [ - { - "name": "Security", - "channel": "Security" - }, - { - "name": "Security", - "channel": "Security" - } - ] -}; - -console.log("\nExample 7 - Invalid Log Source (duplicate permutations):"); -try { - logSourceSchema.parse(invalidLogSourceDuplicatePermutations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// ✖ Duplicate log source permutation found: each (name, channel) pair must be unique -// → at x_mitre_log_source_permutations.x_mitre_log_source_permutations - -/*************************************************************************************************** */ -// Example 8: Valid Log Source (same name, different channels) -/*************************************************************************************************** */ -const validLogSourceSameNameDifferentChannels = { - ...validLogSource, - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a440", - "name": "Sysmon Event Log", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0004", - "url": "https://attack.mitre.org/log-sources/LS0004" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "Sysmon", - "channel": "EventCode=1" - }, - { - "name": "Sysmon", - "channel": "EventCode=3" - }, - { - "name": "Sysmon", - "channel": "EventCode=10" - } - ] -}; - -console.log("\nExample 8 - Valid Log Source (same name, different channels):"); -console.log(`SUCCESS ${logSourceSchema.parse(validLogSourceSameNameDifferentChannels).name}`) - -/*************************************************************************************************** */ -// Example 9: Invalid Log Source (empty permutation name) -/*************************************************************************************************** */ -const invalidLogSourceEmptyPermutationName = { - ...validLogSource, - "x_mitre_log_source_permutations": [ - { - "name": "", - "channel": "Security" - } - ] -}; - -console.log("\nExample 9 - Invalid Log Source (empty permutation name):"); -try { - logSourceSchema.parse(invalidLogSourceEmptyPermutationName); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// ✖ Too small: expected string to have >=1 characters -// → at x_mitre_log_source_permutations[0].name - -/*************************************************************************************************** */ -// Example 10: Invalid Log Source (empty permutation channel) -/*************************************************************************************************** */ -const invalidLogSourceEmptyPermutationChannel = { - ...validLogSource, - "x_mitre_log_source_permutations": [ - { - "name": "Security", - "channel": "" - } - ] -}; - -console.log("\nExample 10 - Invalid Log Source (empty permutation channel):"); -try { - logSourceSchema.parse(invalidLogSourceEmptyPermutationChannel); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// ✖ Too small: expected string to have >=1 characters -// → at x_mitre_log_source_permutations[0].channel - -/*************************************************************************************************** */ -// Example 11: Valid Log Source with Mobile Domain -/*************************************************************************************************** */ -const validMobileLogSource = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a441", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Android System Log", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["mobile-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0020", - "url": "https://attack.mitre.org/log-sources/LS0020" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "logcat", - "channel": "system" - }, - { - "name": "logcat", - "channel": "main" - } - ] -}; - -console.log("\nExample 11 - Valid Mobile Log Source:"); -console.log(`SUCCESS ${logSourceSchema.parse(validMobileLogSource).name}`) - -/*************************************************************************************************** */ -// Example 12: Valid Multi-Domain Log Source -/*************************************************************************************************** */ -const validMultiDomainLogSource = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a442", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Network Traffic", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack", "ics-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0030", - "url": "https://attack.mitre.org/log-sources/LS0030" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "pcap", - "channel": "network" - }, - { - "name": "netflow", - "channel": "flow" - }, - { - "name": "firewall", - "channel": "traffic" - } - ] -}; - -console.log("\nExample 12 - Valid Multi-Domain Log Source:"); -console.log(`SUCCESS ${logSourceSchema.parse(validMultiDomainLogSource).name}`) - -/*************************************************************************************************** */ -// Example 13: Invalid Log Source (invalid domain) -/*************************************************************************************************** */ -const invalidLogSourceDomain = { - ...validLogSource, - "x_mitre_domains": [ - "invalid-domain" - ] -}; - -console.log("\nExample 13 - Invalid Log Source (invalid domain):"); -try { - logSourceSchema.parse(invalidLogSourceDomain); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// ✖ Invalid option: expected one of "enterprise-attack"|"mobile-attack"|"ics-attack" -// → at x_mitre_domains[0] - -/*************************************************************************************************** */ -// Example 14: Valid Log Source with Special Characters -/*************************************************************************************************** */ -const validLogSourceSpecialChars = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a443", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Microsoft-Windows-Security-Auditing/Operational", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0099", - "url": "https://attack.mitre.org/log-sources/LS0099" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "Security/Application-Logs_2024", - "channel": "Microsoft-Windows-Security-Auditing/Operational" - }, - { - "name": "EventLog:Security", - "channel": "EventID=4624" - } - ] -}; - -console.log("\nExample 14 - Valid Log Source with Special Characters:"); -console.log(`SUCCESS ${logSourceSchema.parse(validLogSourceSpecialChars).name}`) - -/*************************************************************************************************** */ -// Example 15: Log Source with unknown property -/*************************************************************************************************** */ -const logSourceWithUnknownProperty = { - ...validLogSource, - foo: 'bar' -} - -console.log("\nExample 15 - Parsing a log source with an unknown property (foo: 'bar'):"); -try { - const parsedLogSource = logSourceSchema.parse(logSourceWithUnknownProperty); - console.log("Parsed successfully. Log Source name:", parsedLogSource.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// ✖ Unrecognized key: "foo" - -/*************************************************************************************************** */ -// Example 16: Invalid Log Source (wrong STIX ID format) -/*************************************************************************************************** */ -const invalidLogSourceStixId = { - ...validLogSource, - "id": "x-mitre-log-source--1" -}; - -console.log("\nExample 16 - Invalid Log Source (wrong STIX ID format):"); -try { - logSourceSchema.parse(invalidLogSourceStixId); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// ✖ Invalid STIX Identifier for LogSource object: contains invalid UUIDv4 format -// → at id - -/*************************************************************************************************** */ -// Example 17: Valid Log Source with Complex Permutations -/*************************************************************************************************** */ -const validLogSourceComplexPermutations = { - "id": "x-mitre-log-source--7c93fd1a-1e6b-4b4e-be42-0d843216a444", - "type": "x-mitre-log-source", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Comprehensive System Monitoring", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "LS0100", - "url": "https://attack.mitre.org/log-sources/LS0100" - } - ], - "x_mitre_log_source_permutations": [ - { - "name": "sysmon:1", - "channel": "process_creation" - }, - { - "name": "auditd:SYSCALL", - "channel": "execve" - }, - { - "name": "powershell:4104", - "channel": "script_block" - }, - { - "name": "wmi:19", - "channel": "wmi_event" - } - ] -}; - -console.log("\nExample 17 - Valid Log Source with Complex Permutations:"); -console.log(`SUCCESS ${logSourceSchema.parse(validLogSourceComplexPermutations).name} (${validLogSourceComplexPermutations.x_mitre_log_source_permutations.length} permutations)`); \ No newline at end of file diff --git a/examples/sdo/malware.example.ts b/examples/sdo/malware.example.ts deleted file mode 100644 index 762debe6..00000000 --- a/examples/sdo/malware.example.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { z } from "zod/v4"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Malware -/** ************************************************************************************************* */ - -const validMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - is_family: false, - x_mitre_version: "1.2" -}; - -console.log("Example 1 - Valid Malware:"); -console.log(malwareSchema.parse(validMalware)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Malware (missing required fields) -/** ************************************************************************************************* */ - -const invalidMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Malware (missing required fields):"); -const e2 = malwareSchema.safeParse(invalidMalware); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Malware with optional fields -/** ************************************************************************************************* */ -const malwareWithOptionalFields = { - ...validMalware, - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_contributors: ["Contributor"], - x_mitre_deprecated: false, - x_mitre_old_attack_id: "MOB-S0123" -}; - -console.log("\nExample 3 - Malware with optional fields:"); -console.log(malwareSchema.parse(malwareWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Malware with invalid type -/** ************************************************************************************************* */ -const malwareWithInvalidType = { - ...validMalware, - type: "invalid-type", -}; - -console.log("\nExample 4 - Malware with invalid type:"); -const e4 = malwareSchema.safeParse(malwareWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Malware with invalid id -/** ************************************************************************************************* */ -const malwareWithInvalidId = { - ...validMalware, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Malware with invalid id:"); -const e5 = malwareSchema.safeParse(malwareWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Malware with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const malwareWithStixFields = { - ...validMalware, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - first_seen: "2015-07-01T00:00:00.000Z", - last_seen: "2016-07-01T00:00:00.000Z", - malware_types: ["remote-access-trojan"], - os_execution_envs: [ - "Windows", - "Linux" - ], - architecture_execution_envs: [ - "x86" - ], - capabilities: [ - "exfiltrates-data", - "accesses-remote-machines" - ], - sample_refs: [ - "file--a3b8b3b2-4d2f-4a2e-9a1b-1c8b3e4e6f5d" - ], - implementation_languages: [ - "python" - ] -}; - -console.log("\nExample 6 - Malware with fields in STIX but not in ATT&CK:"); -console.log(malwareSchema.parse(malwareWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example malware -/** ************************************************************************************************* */ - -const exampleOfRealMalware = { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false -} - -console.log("\nExample 7 - Parsing the provided example malware:"); -const e7 = malwareSchema.safeParse(exampleOfRealMalware); -if (e7.success) { - console.log(e7.data); - console.log("Parsed successfully. Malware name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Malware with unknown property -/** ************************************************************************************************* */ -const malwareWithUnknownProperty = { - ...exampleOfRealMalware, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a malware with an unknown property (foo: 'bar'):"); -const e8 = malwareSchema.safeParse(malwareWithUnknownProperty); -if (e8.success) { - console.log("Parsed successfully. Malware name:", e8.data.name); -} else { - console.log(z.prettifyError(e8.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/matrix.example.ts b/examples/sdo/matrix.example.ts deleted file mode 100644 index c30f4549..00000000 --- a/examples/sdo/matrix.example.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { matrixSchema } from "../../src/schemas/sdo/matrix.schema.js"; -import { z } from "zod/v4"; - -/** ************************************************************************************************* */ -// Example 1: Valid Matrix -/** ************************************************************************************************* */ -const validMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created: "2018-10-17T00:14:20.652Z", - description: - "The full ATT&CK for ICS Matrix includes techniques spanning various ICS assets and can be used to navigate through the knowledge base.", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("Example 1 - Valid Matrix:"); -console.log(matrixSchema.parse(validMatrix)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Matrix (missing required fields) -/** ************************************************************************************************* */ -const invalidMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - created: "2018-10-17T00:14:20.652Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("\nExample 2 - Invalid Matrix (missing required fields):"); -const e2 = matrixSchema.safeParse(invalidMatrix); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Matrix with optional fields -/** ************************************************************************************************* */ -const matrixWithOptionalFields = { - ...validMatrix, - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Matrix with optional fields:"); -console.log(matrixSchema.parse(matrixWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Matrix with invalid type -/** ************************************************************************************************* */ -const matrixWithInvalidType = { - ...validMatrix, - type: "invalid-type", -}; - -console.log("\nExample 4 - Matrix with invalid type:"); -const e4 = matrixSchema.safeParse(matrixWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Matrix -/** ************************************************************************************************* */ -const exampleOfRealMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created: "2018-10-17T00:14:20.652Z", - description: - "The full ATT&CK for ICS Matrix includes techniques spanning various ICS assets and can be used to navigate through the knowledge base.", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("\nExample 5 - Parsing the provided example matrix:"); -const e5 = matrixSchema.safeParse(exampleOfRealMatrix); -if (e5.success) { - console.log("Parsed successfully. Matrix name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Matrix with unknown property -/** ************************************************************************************************* */ -const matrixWithUnknownProperty = { - ...validMatrix, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a matrix with an unknown property (foo: 'bar'):"); -const e6 = matrixSchema.safeParse(matrixWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Matrix name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/mitigation.example.ts b/examples/sdo/mitigation.example.ts deleted file mode 100644 index 7065ed36..00000000 --- a/examples/sdo/mitigation.example.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { z } from "zod/v4"; -import { mitigationSchema } from "../../src/schemas/sdo/mitigation.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Mitigation -/** ************************************************************************************************* */ -const validMitigation = { - type: "course-of-action", - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Mitigation name", - x_mitre_version: "1.0", - description: "Mitigation description", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2018-10-17T00:14:20.652Z", - modified: "2019-07-25T11:22:19.139Z", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/mitigations/M1174", - external_id: "M1174", - }, - { - url: "https://example.com", - description: "Example description.", - source_name: "Example source name", - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", -}; - -console.log("Example 1 - Valid Mitigation:"); -console.log(mitigationSchema.parse(validMitigation)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Mitigation (missing required fields) -/** ************************************************************************************************* */ -const invalidMitigation = { - type: "course-of-action", - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Mitigation name", - x_mitre_version: "1.0", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2018-10-17T00:14:20.652Z", - modified: "2019-07-25T11:22:19.139Z", - x_mitre_domains: ["enterprise-attack"], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", -}; - -console.log("\nExample 2 - Invalid Mitigation (missing required fields):"); -const e2 = mitigationSchema.safeParse(invalidMitigation); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Mitigation with optional fields -/** ************************************************************************************************* */ -const mitigationWithOptionalFields = { - ...validMitigation, - x_mitre_deprecated: false, - revoked: false, - labels: [ - "IEC 62443-3-3:2013 - SR 5.1", - "IEC 62443-4-2:2019 - CR 5.1", - "NIST SP 800-53 Rev. 5 - AC-3; SC-7", - ], - x_mitre_old_attack_id: "MOB-M1008", -}; - -console.log("\nExample 3 - Mitigation with optional fields:"); -console.log(mitigationSchema.parse(mitigationWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Mitigation with invalid type -/** ************************************************************************************************* */ -const mitigationWithInvalidType = { - ...validMitigation, - type: "invalid-type", -}; - -console.log("\nExample 4 - Mitigation with invalid type:"); -const e4 = mitigationSchema.safeParse(mitigationWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Mitigation -/** ************************************************************************************************* */ -const exampleOfRealMitigation = { - x_mitre_domains: ["enterprise-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - type: "course-of-action", - created: "2018-10-17T00:14:20.652Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/mitigations/T1174", - external_id: "T1174", - }, - { - url: "https://msdn.microsoft.com/library/windows/desktop/ms721766.aspx", - description: - "Microsoft. (n.d.). Installing and Registering a Password Filter DLL. Retrieved November 21, 2017.", - source_name: "Microsoft Install Password Filter n.d", - }, - ], - modified: "2019-07-25T11:22:19.139Z", - name: "Password Filter DLL Mitigation", - description: - "Ensure only valid password filters are registered. Filter DLLs must be present in Windows installation directory (C:\\Windows\\System32\\ by default) of a domain controller and/or local computer with a corresponding entry in HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\Notification Packages. (Citation: Microsoft Install Password Filter n.d)", - x_mitre_deprecated: true, - x_mitre_version: "1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", -}; - -console.log("\nExample 5 - Parsing the provided example mitigation:"); -const e5 = mitigationSchema.safeParse(exampleOfRealMitigation); -if (e5.success) { - console.log("Parsed successfully. Mitigation name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Mitigation with unknown property -/** ************************************************************************************************* */ -const mitigationWithUnknownProperty = { - ...validMitigation, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a mitigation with an unknown property (foo: 'bar'):"); -const e6 = mitigationSchema.safeParse(mitigationWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Mitigation name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/software.example.ts b/examples/sdo/software.example.ts deleted file mode 100644 index 659cba6c..00000000 --- a/examples/sdo/software.example.ts +++ /dev/null @@ -1,490 +0,0 @@ -import { z } from "zod/v4"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; -import { toolSchema } from "../../src/schemas/sdo/tool.schema.js"; - -// Malware Examples - -console.log("****************************************************************************************************") -console.log("Malware Examples") -console.log("****************************************************************************************************") -/** ************************************************************************************************* */ -// Example 1: Valid Malware -/** ************************************************************************************************* */ - -const validMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - is_family: false, - x_mitre_version: "1.2" -}; - -console.log("Example 1 - Valid Malware:"); -console.log(malwareSchema.parse(validMalware)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Malware (missing required fields) -/** ************************************************************************************************* */ - -const invalidMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Malware (missing required fields):"); -const e2 = malwareSchema.safeParse(invalidMalware); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Malware with optional fields -/** ************************************************************************************************* */ -const malwareWithOptionalFields = { - ...validMalware, - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_contributors: ["Contributor"], - x_mitre_deprecated: false, - x_mitre_old_attack_id: "MOB-S0123" -}; - -console.log("\nExample 3 - Malware with optional fields:"); -console.log(malwareSchema.parse(malwareWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Malware with invalid type -/** ************************************************************************************************* */ -const malwareWithInvalidType = { - ...validMalware, - type: "invalid-type", -}; - -console.log("\nExample 4 - Malware with invalid type:"); -const e4 = malwareSchema.safeParse(malwareWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Malware with invalid id -/** ************************************************************************************************* */ -const malwareWithInvalidId = { - ...validMalware, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Malware with invalid id:"); -const e5 = malwareSchema.safeParse(malwareWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Malware with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const malwareWithStixFields = { - ...validMalware, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - first_seen: "2015-07-01T00:00:00.000Z", - last_seen: "2016-07-01T00:00:00.000Z", - malware_types: ["remote-access-trojan"], - os_execution_envs: [ - "Windows", - "Linux" - ], - architecture_execution_envs: [ - "x86" - ], - capabilities: [ - "exfiltrates-data", - "accesses-remote-machines" - ], - sample_refs: [ - "file--a3b8b3b2-4d2f-4a2e-9a1b-1c8b3e4e6f5d" - ], - implementation_languages: [ - "python" - ] -}; - -console.log("\nExample 6 - Malware with fields in STIX but not in ATT&CK:"); -console.log(malwareSchema.parse(malwareWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example malware -/** ************************************************************************************************* */ - -const exampleOfRealMalware = { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false -} - -console.log("\nExample 7 - Parsing the provided example malware:"); -const e7 = malwareSchema.safeParse(exampleOfRealMalware); -if (e7.success) { - console.log(e7.data); - console.log("Parsed successfully. Malware name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Malware with unknown property -/** ************************************************************************************************* */ -const malwareWithUnknownProperty = { - ...exampleOfRealMalware, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a malware with an unknown property (foo: 'bar'):"); -const e8 = malwareSchema.safeParse(malwareWithUnknownProperty); -if (e8.success) { - console.log("Parsed successfully. Malware name:", e8.data.name); -} else { - console.log(z.prettifyError(e8.error as z.core.$ZodError)); -} - -// Tool Examples - -console.log("****************************************************************************************************") -console.log("Tool Examples") -console.log("****************************************************************************************************") - -/** ************************************************************************************************* */ -// Example 1: Valid Tool -/** ************************************************************************************************* */ - -const validTool = { - type: 'tool', - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2021-07-30T15:43:17.770Z", - modified: "2024-04-11T00:06:01.264Z", - name: "Sliver", - description: '[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)', - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0049", - external_id: "S0049" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.2", - x_mitre_attack_spec_version: "3.2.0" -}; - -console.log("Example 1 - Valid Tool:"); -console.log(toolSchema.parse(validTool)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Tool (missing required fields) -/** ************************************************************************************************* */ - -const invalidTool = { - type: 'tool', - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', - spec_version: '2.1', - created: "2021-07-30T15:43:17.770Z", - modified: "2024-04-11T00:06:01.264Z", - name: "Sliver", - description: '[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)', - x_mitre_aliases: [ - "Sliver" - ], - x_mitre_contributors: [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - x_mitre_deprecated: false, - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_platforms: [ - "Windows", - "Linux", - "macOS" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Tool (missing required fields):"); -const et2 = toolSchema.safeParse(invalidTool); -console.log(z.prettifyError(et2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Tool with optional fields -/** ************************************************************************************************* */ -const toolWithOptionalFields = { - ...validTool, - x_mitre_contributors: [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - x_mitre_aliases: [ - "Sliver" - ], - x_mitre_deprecated: false, - x_mitre_platforms: [ - "Windows", - "Linux", - "macOS" - ] -}; - -console.log("\nExample 3 - Tool with optional fields:"); -console.log(toolSchema.parse(toolWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Tool with invalid type -/** ************************************************************************************************* */ -const toolWithInvalidType = { - ...validTool, - type: "invalid-type", -}; - -console.log("\nExample 4 - Tool with invalid type:"); -const et4 = toolSchema.safeParse(toolWithInvalidType); -console.log(z.prettifyError(et4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Tool with invalid id -/** ************************************************************************************************* */ -const toolWithInvalidId = { - ...validTool, - id: 'malware--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Tool with invalid id:"); -const et5 = toolSchema.safeParse(toolWithInvalidId); -console.log(z.prettifyError(et5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Tool with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const toolWithStixFields = { - ...validTool, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "Sliver" - ], - tool_types: ["remote-access"], - tool_version: "1.0" -}; - -console.log("\nExample 6 - Tool with fields in STIX but not in ATT&CK:"); -console.log(toolSchema.parse(toolWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example tool -/** ************************************************************************************************* */ - -const exampleOfRealTool = { - "type": "tool", - "id": "tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2021-07-30T15:43:17.770Z", - "modified": "2024-04-11T00:06:01.264Z", - "name": "Sliver", - "description": "[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)", - "labels": [ - "tool" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0633", - "external_id": "S0633" - }, - { - "source_name": "Bishop Fox Sliver Framework August 2019", - "description": "Kervella, R. (2019, August 4). Cross-platform General Purpose Implant Framework Written in Golang. Retrieved July 30, 2021.", - "url": "https://labs.bishopfox.com/tech-blog/sliver" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "Sliver" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_contributors": [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows", - "Linux", - "macOS" - ], - "x_mitre_version": "1.2", - "spec_version": "2.0" -} - -console.log("\nExample 7 - Parsing the provided example tool:"); -const et7 = toolSchema.safeParse(exampleOfRealTool); -if (et7.success) { - console.log(et7.data); - console.log("Parsed successfully. Tool name:", et7.data.name); -} else { - console.log(z.prettifyError(et7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Tool with unknown property -/** ************************************************************************************************* */ -const toolWithUnknownProperty = { - ...exampleOfRealTool, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a tool with an unknown property (foo: 'bar'):"); -const et8 = toolSchema.safeParse(toolWithUnknownProperty); -if (et8.success) { - console.log("Parsed successfully. Tool name:", et8.data.name); -} else { - console.log(z.prettifyError(et8.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/stix-bundle.example.ts b/examples/sdo/stix-bundle.example.ts deleted file mode 100644 index f0527b63..00000000 --- a/examples/sdo/stix-bundle.example.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { z } from "zod/v4"; -import { stixBundleSchema } from '../../src/schemas/sdo/stix-bundle.schema.js'; -import type { StixCreatedTimestamp, StixModifiedTimestamp } from "../../src/schemas/common/index.js"; -import { v4 as uuidv4 } from 'uuid'; -import { identitySchema } from "../../src/schemas/sdo/identity.schema.js"; -import { assetSchema } from "../../src/schemas/sdo/asset.schema.js"; -import { campaignSchema } from "../../src/schemas/sdo/campaign.schema.js"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; -import { matrixSchema } from "../../src/schemas/sdo/matrix.schema.js"; -import { toolSchema } from "../../src/schemas/sdo/tool.schema.js"; -import { groupSchema } from "../../src/schemas/sdo/group.schema.js"; -import { mitigationSchema } from "../../src/schemas/sdo/mitigation.schema.js"; -import { dataComponentSchema } from "../../src/schemas/sdo/data-component.schema.js"; -import { dataSourceSchema } from "../../src/schemas/sdo/data-source.schema.js"; -import { tacticSchema } from "../../src/schemas/sdo/tactic.schema.js"; -import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; -import { collectionSchema } from "../../src/schemas/sdo/collection.schema.js"; -import { markingDefinitionSchema } from "../../src/schemas/smo/marking-definition.schema.js"; -import { relationshipSchema } from "../../src/schemas/sro/relationship.schema.js"; - -const StixObjectSchema: {[key: string]: z.ZodSchema} = { - "x-mitre-asset": assetSchema, - "campaign": campaignSchema, - "x-mitre-collection": collectionSchema, - "x-mitre-data-component": dataComponentSchema, - "x-mitre-data-source": dataSourceSchema, - "intrusion-set": groupSchema, - "identity": identitySchema, - "malware": malwareSchema, - "marking-definition": markingDefinitionSchema, - "x-mitre-matrix": matrixSchema, - "course-of-action": mitigationSchema, - "x-mitre-tactic": tacticSchema, - "attack-pattern": techniqueSchema, - "tool": toolSchema, - "relationship": relationshipSchema -}; - -/** ************************************************************************************************* */ -// Example 1: Valid Stix Bundle -/** ************************************************************************************************* */ - -const minimalCollection = { - id: `x-mitre-collection--${uuidv4()}`, - type: 'x-mitre-collection', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - modified: '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - name: 'Test Collection', - description: 'This is a test collection.', - x_mitre_attack_spec_version: "2.1.0", - x_mitre_version: "1.0", - x_mitre_contents: [ - { - object_ref: "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - object_modified: "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] -}; - -const validBundle = { - id: `bundle--${uuidv4()}`, - type: 'bundle', - spec_version: '2.1', - objects: [minimalCollection], -}; - -console.log("Example 1 - Valid Stix Bundle:"); -console.log(stixBundleSchema.parse(validBundle)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Stix Bundle (missing required fields) -/** ************************************************************************************************* */ - -const invalidBundle = { - id: `bundle--${uuidv4()}`, - objects: [minimalCollection] -}; - -console.log("Example 2 - Invalid Stix Bundle (missing required fields):"); -const e2 = stixBundleSchema.safeParse(invalidBundle); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Invalid Stix Bundle (missing required fields) -/** ************************************************************************************************* */ - -const bundleWithInvalidCollection = { - id: `bundle--${uuidv4()}`, - type: 'bundle', - spec_version: '2.1', - objects: [ - { - id: `x-mitre-collection--${uuidv4()}`, - type: 'x-mitre-collection', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - modified: '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - name: 'Test Collection', - description: 'This is a test collection.', - x_mitre_attack_spec_version: "2.1.0", - x_mitre_version: "1.0", - x_mitre_contents: [ - { - object_ref: "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - object_modified: "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] - }, - { - type: "identity", - id: `identity--${uuidv4()}`, - spec_version: "2.3", - created: "2017-06-01T00:00:00.000Z" as StixCreatedTimestamp, - modified: "2017-06-01T00:00:00.000Z" as StixModifiedTimestamp, - name: "The MITRE Corporation", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: ["enterprise-attack"], - x_mitre_version: "1.0" - } - ] -}; -console.log("Example 3 - Invalid Collection (missing required fields):"); - -const e3 = stixBundleSchema.safeParse(bundleWithInvalidCollection); -if (!e3.success) { - const errors: string[] = []; - e3.error.issues.forEach((issue) => { - const objectIndex = issue.path.find((p) => typeof p === 'number'); - const errorObject = objectIndex !== undefined ? bundleWithInvalidCollection.objects[objectIndex as number] : undefined; - console.log("\n") - let errorMessage = `Error in bundle`; - let objectMessage = `Validation errors: `; - if (errorObject) { - errorMessage += `\n Object Index: ${objectIndex}`; - errorMessage += `\n Object ID: ${errorObject.id}`; - errorMessage += `\n Object Type: ${errorObject.type}`; - errorMessage += `\n Object Name: ${(errorObject as any).name || 'N/A'}`; - - let objectStatus = 'Active'; - if ((errorObject as any).x_mitre_deprecated) { - objectStatus = 'Deprecated'; - } - errorMessage += `\n Object Status: ${objectStatus}`; - const schema = StixObjectSchema[errorObject.type]; - const objValidation = schema.safeParse(errorObject); - if (!objValidation.success) { - objectMessage += objValidation.error.issues.map(err => `\n - ${err.path.join('.')} : ${err.message}`).join(''); - } - } - errorMessage += `\n Path: ${issue.path.join('.')}`; - errorMessage += `\n Error: ${issue.message}`; - errors.push(errorMessage); - console.warn(errorMessage); - console.warn(objectMessage); - }); -} - -/** ************************************************************************************************* */ -// Example 4: Stix Bundle with invalid type -/** ************************************************************************************************* */ -const stixBundleWithInvalidType = { - ...validBundle, - type: "invalid-type", -}; - -console.log("\nExample 4 - Stix Bundle with invalid type:"); -const e4 = stixBundleSchema.safeParse(stixBundleWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Stix Bundle with invalid id -/** ************************************************************************************************* */ -const stixBundleWithInvalidId = { - ...validBundle, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Stix Bundle with invalid id:"); -const e5 = stixBundleSchema.safeParse(stixBundleWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example stix bundle -/** ************************************************************************************************* */ - -const exampleOfRealStixBundle = { - "id": `bundle--${uuidv4()}`, - "type": 'bundle', - "spec_version": '2.1', - "objects": [ - { - "id": `x-mitre-collection--${uuidv4()}`, - "type": 'x-mitre-collection', - "spec_version": '2.1', - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - "modified": '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "name": 'Test Collection', - "description": 'This is a test collection.', - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_version": "1.0", - "x_mitre_contents": [ - { - "object_ref": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - "object_modified": "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] - }, - { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false - } - ] -} - -console.log("\nExample 6 - Parsing the provided example stixBundle:"); -const e6 = stixBundleSchema.safeParse(exampleOfRealStixBundle); -if (e6.success) { - console.log(e6.data); - console.log("Parsed successfully. stix bundle ID:", e6.data.id); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: Stix Bundle with unknown property -/** ************************************************************************************************* */ -const stixBundleWithUnknownProperty = { - ...exampleOfRealStixBundle, - foo: 'bar', -} - -console.log("\nExample 7 - Parsing a stix bundle with an unknown property (foo: 'bar'):"); -const e7 = stixBundleSchema.safeParse(stixBundleWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. Stix Bundle name:", e7.data.id); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/tactic.example.ts b/examples/sdo/tactic.example.ts deleted file mode 100644 index 416d28dc..00000000 --- a/examples/sdo/tactic.example.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { z } from "zod/v4"; -import { tacticSchema } from "../../src/schemas/sdo/tactic.schema.js"; - -/****************************************************************************************************/ -// Example 1: Valid Tactic -/****************************************************************************************************/ -const validTactic = { - "id": "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "name": "Execution", - "description": "The adversary is trying to run malicious code.\n\nExecution consists of techniques that result in adversary-controlled code running on a local or remote system. Techniques that run malicious code are often paired with techniques from all other tactics to achieve broader goals, like exploring a network or stealing data. For example, an adversary might use a remote access tool to run a PowerShell script that does Remote System Discovery. ", - "external_references": [ - { - "external_id": "TA0002", - "url": "https://attack.mitre.org/tactics/TA0002", - "source_name": "mitre-attack" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_shortname": "execution", - "type": "x-mitre-tactic", - "modified": "2019-07-19T17:42:06.909Z", - "created": "2018-10-17T00:14:20.652Z", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0" -}; - -console.log("\nExample 1: Valid Tactic:"); -console.log(`SUCCESS ${tacticSchema.parse(validTactic).name}`) - -/****************************************************************************************************/ -// Example 2: Invalid Tactic (ATT&CK ID does not match format TA####) -/****************************************************************************************************/ -const invalidTacticID = { - ...validTactic, - external_references: [ - { - source_name: "mitre-attack", - external_id: "X0000" - } - ] -}; - -console.log("\nExample 2: Invalid Tactic (ATT&CK ID does not match format TA####):"); -const e2 = tacticSchema.safeParse(invalidTacticID); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 3: Invalid Tactic (missing required fields) -/****************************************************************************************************/ -const invalidTacticMissingFields = { - "id": "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "description": "The adversary is trying to run malicious code.\n\nExecution consists of techniques that result in adversary-controlled code running on a local or remote system. Techniques that run malicious code are often paired with techniques from all other tactics to achieve broader goals, like exploring a network or stealing data. For example, an adversary might use a remote access tool to run a PowerShell script that does Remote System Discovery. ", - "external_references": [ - { - "external_id": "TA0002", - "url": "https://attack.mitre.org/tactics/TA0002", - "source_name": "mitre-attack" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "type": "x-mitre-tactic", - "modified": "2019-07-19T17:42:06.909Z", - "created": "2018-10-17T00:14:20.652Z", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" -}; - -console.log("\nExample 3: Invalid Tactic (missing required fields):"); -const e3 = tacticSchema.safeParse(invalidTacticMissingFields); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 4: Tactic with invalid type -/****************************************************************************************************/ -const tacticWithInvalidType = { - ...validTactic, - type: "invalid-type" -}; - -console.log("\nExample 4: Tactic with invalid type:"); -const e4 = tacticSchema.safeParse(tacticWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 5: Tactic with optional fields -/****************************************************************************************************/ -const tacticWithOptionalFields = { - ...validTactic, - x_mitre_deprecated: true -} - -console.log("\nExample 5: Tactic with optional fields:"); -console.log(tacticSchema.parse(tacticWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 6: Tactic with unknown property -/** ************************************************************************************************* */ -const tacticWithUnknownProperty = { - ...validTactic, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a tactic with an unknown property (foo: 'bar'):"); -const e6 = tacticSchema.safeParse(tacticWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Tactic name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/technique.example.ts b/examples/sdo/technique.example.ts deleted file mode 100644 index 34cc8089..00000000 --- a/examples/sdo/technique.example.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { z } from "zod/v4"; -import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Technique -/*************************************************************************************************** */ -const validEnterpriseTechnique = { - "modified": "2024-02-02T19:04:35.389Z", - "name": "Data Obfuscation", - "description": "Adversaries may obfuscate command and control traffic to make it more difficult to detect.(Citation: Bitdefender FunnyDream Campaign November 2020) Command and control (C2) communications are hidden (but not necessarily encrypted) in an attempt to make the content more difficult to discover or decipher and to make the communication less conspicuous and hide commands from being seen. This encompasses many methods, such as adding junk data to protocol traffic, using steganography, or impersonating legitimate protocols. ", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Linux", - "macOS", - "Windows" - ], - "x_mitre_version": "1.1", - "type": "attack-pattern", - "id": "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - "created": "2017-05-31T21:30:18.931Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "T1001" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 1 - Valid Technique:"); -console.log(`SUCCESS ${techniqueSchema.parse(validEnterpriseTechnique).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Technique (ATT&CK ID does not match format T####) -/*************************************************************************************************** */ -const invalidTechniqueID = { - ...validEnterpriseTechnique, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "A00" - } - ], -}; - -console.log("\nExample 2 - Invalid Technique (ATT&CK ID does not match format T####):"); -const e2 = techniqueSchema.safeParse(invalidTechniqueID); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 3: Valid Sub-technique -/*************************************************************************************************** */ -const validSubtechnique = { - "modified": "2023-03-20T18:43:03.218Z", - "name": "Uninstall Malicious Application", - "description": "Adversaries may include functionality in malware that uninstalls the malicious application from the device. This can be achieved by: \n \n* Abusing device owner permissions to perform silent uninstallation using device owner API calls. \n* Abusing root permissions to delete files from the filesystem. \n* Abusing the accessibility service. This requires sending an intent to the system to request uninstallation, and then abusing the accessibility service to click the proper places on the screen to confirm uninstallation.", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-mobile-attack", - "phase_name": "defense-evasion" - } - ], - "x_mitre_deprecated": false, - "x_mitre_detection": "Users can see a list of applications that can use accessibility services in the device settings. Application vetting services could look for use of the accessibility service or features that typically require root access.", - "x_mitre_domains": [ - "mobile-attack" - ], - "x_mitre_is_subtechnique": true, - "x_mitre_platforms": [ - "Android" - ], - "x_mitre_version": "1.1", - "x_mitre_tactic_type": [ - "Post-Adversary Device Access" - ], - "type": "attack-pattern", - "id": "attack-pattern--0cdd66ad-26ac-4338-a764-4972a1e17ee3", - "created": "2022-03-30T19:31:31.855Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1630/001", - "external_id": "T1630.001" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-43.html", - "external_id": "APP-43" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -} -console.log("\nExample 3 - Valid Subtechnique:"); -console.log(`SUCCESS ${techniqueSchema.parse(validSubtechnique).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Sub-technique (ATT&CK ID does not match format T####.###) -/*************************************************************************************************** */ -const invalidSubtechniqueID = { - ...validSubtechnique, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1630/001", - "external_id": "T1630" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-43.html", - "external_id": "APP-43" - } - ], -} - -console.log("\nExample 4 - Invalid Subtechnique (ATT&CK ID does not match format T####.###):"); -const e4 = techniqueSchema.safeParse(invalidSubtechniqueID); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 5: Invalid Technique (missing required fields) -/*************************************************************************************************** */ -const invalidTechniqueMissingFields = { - "modified": "2024-02-02T19:04:35.389Z", - "description": "Adversaries may obfuscate command and control traffic to make it more difficult to detect.(Citation: Bitdefender FunnyDream Campaign November 2020) Command and control (C2) communications are hidden (but not necessarily encrypted) in an attempt to make the content more difficult to discover or decipher and to make the communication less conspicuous and hide commands from being seen. This encompasses many methods, such as adding junk data to protocol traffic, using steganography, or impersonating legitimate protocols. ", - "x_mitre_deprecated": false, - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Linux", - "macOS", - "Windows" - ], - "type": "attack-pattern", - "id": "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - "created": "2017-05-31T21:30:18.931Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "T1001" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 5 - Invalid Technique (missing required fields):"); -const e5 = techniqueSchema.safeParse(invalidTechniqueMissingFields); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 6: Technique with invalid type -/*************************************************************************************************** */ -const techniqueWithInvalidType = { - ...validEnterpriseTechnique, - "type": 'invalid-type' -} - -console.log("\nExample 6 - Technique with invalid type:"); -const e6 = techniqueSchema.safeParse(techniqueWithInvalidType); -console.log(z.prettifyError(e6.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 7: Valid Enterprise Technique with Enterprise-only fields -/*************************************************************************************************** */ -const validTechniqueWithEnterpriseFields = { - ...validEnterpriseTechnique, - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "privilege-escalation" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "defense-evasion" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "execution" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "impact" - } - ], - "x_mitre_system_requirements": ["Windows 10"], - "x_mitre_permissions_required": ["User"], - "x_mitre_effective_permissions": ["Administrator"], - "x_mitre_defense_bypassed": ["Anti-virus"], - "x_mitre_remote_support": true, - "x_mitre_impact_type": ["Integrity"] -}; -console.log("\nExample 7: Valid Enterprise Technique with Enterprise-only fields:"); -let result = techniqueSchema.parse(validTechniqueWithEnterpriseFields); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 8: Invalid Enterprise Technique with Mobile-only fields -/*************************************************************************************************** */ -const invalidEnterpriseTechnique = { - ...validEnterpriseTechnique, - "x_mitre_tactic_type": ["Post-Adversary Device Access"], -}; - -console.log("\nExample 8: Invalid Enterprise Technique with Mobile-only fields:"); -const e8 = techniqueSchema.safeParse(invalidEnterpriseTechnique); -console.log(z.prettifyError(e8.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 9: Valid Mobile Technique with Mobile-only fields -/*************************************************************************************************** */ -const validMobileTechnique = { - "modified": "2023-03-15T16:23:59.281Z", - "name": "Abuse Elevation Control Mechanism", - "description": "Adversaries may circumvent mechanisms designed to control elevated privileges to gain higher-level permissions. Most modern systems contain native elevation control mechanisms that are intended to limit privileges that a user can gain on a machine. Authorization has to be granted to specific users in order to perform tasks that are designated as higher risk. An adversary can use several methods to take advantage of built-in control mechanisms in order to escalate privileges on a system. ", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-mobile-attack", - "phase_name": "privilege-escalation" - } - ], - "x_mitre_deprecated": false, - "x_mitre_detection": "When an application requests administrator permission, users are presented with a popup and the option to grant or deny the request. Application vetting services can detect when an application requests administrator permission. Extra scrutiny could be applied to applications that do", - "x_mitre_domains": [ - "mobile-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Android" - ], - "x_mitre_version": "1.1", - "x_mitre_tactic_type": [ - "Post-Adversary Device Access" - ], - "type": "attack-pattern", - "id": "attack-pattern--08ea902d-ecb5-47ed-a453-2798057bb2d3", - "created": "2022-04-01T15:54:05.633Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1626", - "external_id": "T1626" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-22.html", - "external_id": "APP-22" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -} - -console.log("\nExample 9: Valid Mobile Technique with Mobile-only fields:"); -result = techniqueSchema.parse(validMobileTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 10: Invalid Mobile Technique with Enterprise-only fields -/*************************************************************************************************** */ -const invalidMobileTechnique = { - ...validMobileTechnique, - "x_mitre_system_requirements": ["system requirements"] -} - -console.log("\nExample 10: Invalid Mobile Technique with Enterprise-only fields:"); -const e10 = techniqueSchema.safeParse(invalidMobileTechnique); -console.log(z.prettifyError(e10.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 11: Valid ICS Technique with ICS-only fields -/*************************************************************************************************** */ -const validIcsTechnique = { - "modified": "2023-10-13T17:56:58.380Z", - "name": "Block Command Message", - "description": "Adversaries may block a command message from reaching its intended target to prevent command execution. In OT networks, command messages are sent to provide instructions to control system devices. A blocked command message can inhibit response functions from correcting a disruption or unsafe condition. (Citation: Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011) (Citation: Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems March 2016)", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-ics-attack", - "phase_name": "inhibit-response-function" - } - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "ics-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "None" - ], - "x_mitre_version": "1.1", - "x_mitre_data_sources": [ - "Process: Process Termination", - "Operational Databases: Process History/Live Data", - "Application Log: Application Log Content", - "Network Traffic: Network Traffic Flow", - "Operational Databases: Process/Event Alarm" - ], - "type": "attack-pattern", - "id": "attack-pattern--008b8f56-6107-48be-aa9f-746f927dbb61", - "created": "2020-05-21T17:43:26.506Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T0803", - "external_id": "T0803" - }, - { - "source_name": "Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011", - "description": "Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011 A Taxonomy of Cyber Attacks on SCADA Systems Retrieved. 2018/01/12 ", - "url": "http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6142258" - }, - { - "source_name": "Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems March 2016", - "description": "Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems 2016, March 18 Analysis of the Cyber Attack on the Ukranian Power Grid: Defense Use Case Retrieved. 2018/03/27 ", - "url": "https://assets.contentstack.io/v3/assets/blt36c2e63521272fdc/blt6a77276749b76a40/607f235992f0063e5c070fff/E-ISAC_SANS_Ukraine_DUC_5%5b73%5d.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "spec_version": "2.1" -} - -console.log("\nExample 11: Valid ICS Technique with ICS-only fields:"); -result = techniqueSchema.parse(validIcsTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 12: Invalid ICS Technique with Enterprise-only fields -/*************************************************************************************************** */ -const invalidIcsTechnique = { - ...validIcsTechnique, - "x_mitre_permissions_required": ["permissions required"] -} - -console.log("\nExample 12: Invalid ICS Technique with Enterprise-only fields:"); -const e12 = techniqueSchema.safeParse(invalidIcsTechnique); -console.log(z.prettifyError(e12.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 13: Valid multi-domain Technique with Enterprise/ICS-only fields -/*************************************************************************************************** */ -const validMultiDomainTechnique = { - ...validEnterpriseTechnique, - "x_mitre_domains": [ - "enterprise-attack", - "ics-attack" - ], - "x_mitre_data_sources": [ - "Process: Process Termination", - "Operational Databases: Process History/Live Data", - "Application Log: Application Log Content", - "Network Traffic: Network Traffic Flow", - "Operational Databases: Process/Event Alarm" - ], -} - -console.log("\nExample 13: Valid multi-domain Technique with Enterprise/ICS-only fields:"); -result = techniqueSchema.parse(validMultiDomainTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 14: Enterprise-only fields in the wrong tactic -/*************************************************************************************************** */ -console.log("\nExample 14: Invalid Enterprise Technique with Enterprise-only field in wrong tactic:"); -const invalidEnterpriseTechniqueWrongTactic = { - ...validEnterpriseTechnique, - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "execution" - } - ], - "x_mitre_permissions_required": ["User"] -}; - -const e14 = techniqueSchema.safeParse(invalidEnterpriseTechniqueWrongTactic); -console.log(z.prettifyError(e14.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 15: Technique with unknown property -/** ************************************************************************************************* */ -const techniqueWithUnknownProperty = { - ...validEnterpriseTechnique, - foo: 'bar' -} - -console.log("\nExample 15 - Parsing a technique with an unknown property (foo: 'bar'):"); -const e15 = techniqueSchema.safeParse(techniqueWithUnknownProperty); -if (e15.success) { - console.log("Parsed successfully. Technique name:", e15.data.name); -} else { - console.log(z.prettifyError(e15.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/smo/marking-definition.example.ts b/examples/smo/marking-definition.example.ts deleted file mode 100644 index de072770..00000000 --- a/examples/smo/marking-definition.example.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { markingDefinitionSchema } from "../../src/schemas/smo/marking-definition.schema.js"; -import { z } from "zod"; - -/** ************************************************************************************************* */ -// Example 1: Valid Marking Definition -/** ************************************************************************************************* */ -const validMarkingDefinition = { - definition: { - statement: "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - created: "2017-06-01T00:00:00.000Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - definition_type: "statement", - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("Example 1 - Valid Marking Definition:"); -console.log(markingDefinitionSchema.parse(validMarkingDefinition)); -// { -// id: 'marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168', -// type: 'marking-definition', -// spec_version: '2.1', -// created: '2017-06-01T00:00:00.000Z', -// created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', -// definition_type: 'statement', -// definition: { -// statement: 'Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.' -// }, -// x_mitre_domains: [ 'mobile-attack' ], -// x_mitre_attack_spec_version: '2.1.0' -// } - - -/** ************************************************************************************************* */ -// Example 2: Invalid Marking Definition (missing required fields) -/** ************************************************************************************************* */ -const invalidMarkingDefinition = { - definition: { - statement: - "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - // missing created - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // missing definition_type - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log( - "\nExample 2 - Invalid Marking Definition (missing required fields):" -); -try { - markingDefinitionSchema.parse(invalidMarkingDefinition); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Validation errors: [ -// { -// code: 'custom', -// message: "Invalid STIX timestamp format: must be an RFC3339 timestamp with a timezone specification of 'Z'.", -// fatal: true, -// path: [ 'created' ] -// }, -// { -// expected: "'statement' | 'tlp'", -// received: 'undefined', -// code: 'invalid_type', -// path: [ 'definition_type' ], -// message: "definition_type must be either 'statement' or 'tlp'" -// } -// ] - -/** ************************************************************************************************* */ -// Example 3: Marking Definition with invalid fields -/** ************************************************************************************************* */ -const invalidDefinitionStatement = { - statement: "Example statement", - name: "Example name", // <--- This property is not allowed on definition statements - external_references: [ // <--- This property is not allowed on definition statements - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0698", - external_id: "S0698", - }, - ], - object_marking_refs: [ // <--- This property is not allowed on definition statements - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", // <--- This property is not allowed on definition statements -} - -const markingDefinitionWithOptionalFields = { - ...validMarkingDefinition, - definition: invalidDefinitionStatement, -}; - -console.log("\nExample 3 - Marking Definition with optional fields:"); -try { - markingDefinitionSchema.parse(markingDefinitionWithOptionalFields); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Validation errors: [ -// { -// code: 'unrecognized_keys', -// keys: [ -// 'name', -// 'external_references', -// 'object_marking_refs', -// 'created_by_ref' -// ], -// path: [ 'definition' ], -// message: "Unrecognized key(s) in object: 'name', 'external_references', 'object_marking_refs', 'created_by_ref'" -// } -// ] - -/** ************************************************************************************************* */ -// Example 4: Marking Definition with invalid type -/** ************************************************************************************************* */ -const markingDefinitionWithInvalidType = { - ...validMarkingDefinition, - type: "invalid-type", -}; - -console.log("\nExample 4 - Marking Definition with invalid type:"); -try { - markingDefinitionSchema.parse(markingDefinitionWithInvalidType); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation error:", error.errors[0].message); - // Validation error: Invalid literal value, expected "marking-definition" - } -} - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Marking Definition -/** ************************************************************************************************* */ -const exampleOfRealMarkingDefinition = { - definition: { - statement: - "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - created: "2017-06-01T00:00:00.000Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - definition_type: "statement", - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 5 - Parsing the provided example Marking Definition:"); -try { - const parsedMarkingDefinition = markingDefinitionSchema.parse( - exampleOfRealMarkingDefinition - ); - console.log(`Parsed successfully. marking definition id: ${parsedMarkingDefinition.id}`); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Parsed successfully. Marking Definition id: marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168 diff --git a/tsconfig.json b/tsconfig.json index c947c1e9..d4387ea4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,4 @@ -{ +{ // These settings were informed by the TypeScript documentation for writing TypeScript libraries: // https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-a-library "compilerOptions": { @@ -22,10 +22,9 @@ "rootDir": "src", // Configures module resolution paths, allowing "@/" to resolve to "./src/" "paths": { - "@/*": [ - "./src/*" - ] - }, + "@/*": ["./src/*"], + "@mitre-attack/attack-data-model": ["./src/index.ts"] + } }, // Specifies which files to include in the compilation "include": [ From 11d415928d84390e6983624e389a6dec410ef774 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 14 Aug 2025 16:57:30 -0500 Subject: [PATCH 10/39] fix: remove spec_version from STIX bundle object --- src/main.ts | 1 - src/schemas/common/stix-spec-version.ts | 4 +--- src/schemas/sdo/stix-bundle.schema.ts | 7 ------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main.ts b/src/main.ts index 6d5cce8a..12512799 100644 --- a/src/main.ts +++ b/src/main.ts @@ -203,7 +203,6 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO .pick({ id: true, type: true, - spec_version: true, }) .safeParse(rawData); diff --git a/src/schemas/common/stix-spec-version.ts b/src/schemas/common/stix-spec-version.ts index ce047634..8263a876 100644 --- a/src/schemas/common/stix-spec-version.ts +++ b/src/schemas/common/stix-spec-version.ts @@ -7,8 +7,6 @@ const specVersionDescription = [ 'Since SCOs are now top-level objects in STIX 2.1, the default value for SCOs is 2.1.', ].join(' '); -export const stixSpecVersionSchema = z - .enum(['2.0', '2.1']) - .meta({ description: specVersionDescription }); +export const stixSpecVersionSchema = z.literal('2.1').meta({ description: specVersionDescription }); export type StixSpecVersion = z.infer; diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 6b264d68..2eb8c09c 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -1,7 +1,6 @@ import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { createStixIdValidator } from '../common/stix-identifier.js'; -import { type StixSpecVersion, stixSpecVersionSchema } from '../common/stix-spec-version.js'; import { createStixTypeValidator } from '../common/stix-type.js'; import { type MarkingDefinition, @@ -179,13 +178,7 @@ export type AttackObjects = z.infer; export const stixBundleSchema = z .object({ id: createStixIdValidator('bundle'), - type: createStixTypeValidator('bundle'), - - spec_version: z - .literal(stixSpecVersionSchema.enum['2.1'] as StixSpecVersion) - .meta({ description: 'Only STIX 2.1 specification version is allowed' }), - objects: attackObjectsSchema, }) .strict() From ad381967236d24123445e9947044f14eb7a3a4e5 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 14 Aug 2025 16:58:11 -0500 Subject: [PATCH 11/39] feat: add export from fetch-attack-version --- src/data-sources/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data-sources/index.ts b/src/data-sources/index.ts index 2b892937..a7eea2fb 100644 --- a/src/data-sources/index.ts +++ b/src/data-sources/index.ts @@ -1 +1,2 @@ export * from './data-source-registration.js'; +export * from './fetch-attack-versions.js'; From 3e61daaf7fdf33842aefffd29324a0e75aa09d68 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 14 Aug 2025 17:03:21 -0500 Subject: [PATCH 12/39] test: remove invalid spec_version from STIX bundle test --- test/objects/stix-bundle.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/objects/stix-bundle.test.ts b/test/objects/stix-bundle.test.ts index ee0435d4..bca84032 100644 --- a/test/objects/stix-bundle.test.ts +++ b/test/objects/stix-bundle.test.ts @@ -42,7 +42,6 @@ describe('StixBundleSchema', () => { minimalBundle = { id: `bundle--${uuidv4()}`, type: 'bundle', - spec_version: '2.1', objects: [minimalCollection], }; }); @@ -119,10 +118,6 @@ describe('StixBundleSchema', () => { testField('type', 'invalid-type'); }); - describe('spec_version', () => { - testField('spec_version', 'invalid-version'); - }); - describe('objects', () => { it('should reject invalid objects array (non-array value)', () => { const invalidObjectsArray = { From 185d4184a2f67f4731d53fc8de32aa41868ebd02 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 14 Aug 2025 17:05:40 -0500 Subject: [PATCH 13/39] chore: update version of ATT&CK to 17.1 in first-query example --- examples/first-query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/first-query.ts b/examples/first-query.ts index 32ece5c9..bb72b172 100644 --- a/examples/first-query.ts +++ b/examples/first-query.ts @@ -7,7 +7,7 @@ async function exploreAttackData() { const dataSource = new DataSourceRegistration({ source: 'attack', // Load from official ATT&CK repository domain: 'enterprise-attack', // Focus on Enterprise domain - version: '15.1', // Use specific version for consistency + version: '17.1', // Use specific version for consistency parsingMode: 'relaxed', // Continue even if some data has minor issues }); From 1f892bcd7a1193fd13f3197af83651b3eca595c0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 08:53:27 -0400 Subject: [PATCH 14/39] chore: restore docusaurus .gitignore --- docusaurus/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docusaurus/.gitignore b/docusaurus/.gitignore index b2d6de30..e5394fb1 100644 --- a/docusaurus/.gitignore +++ b/docusaurus/.gitignore @@ -18,3 +18,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +docs/*/* +!docs/sdo/stix-bundle.schema.md From aa61953510147b098076282c8535a7f866cfa4fd Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:29:56 -0400 Subject: [PATCH 15/39] fix: prevent objects key from being dropped during bundle validation The strict schema validation was rejecting the 'objects' key when using .pick to validate only bundle 'id' and 'type' keys, causing AttackDataModel instances with zero objects loaded. Added .loose() to allow the 'objects' key to pass through validation. Fixes issue where registerDataSource() would return empty data models despite successful data fetching from ATT&CK GitHub repo. --- src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.ts b/src/main.ts index 12512799..4ba0b007 100644 --- a/src/main.ts +++ b/src/main.ts @@ -204,6 +204,7 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO id: true, type: true, }) + .loose() // <--- required to let `objects` pass-through without validation (otherwise it gets dropped and the ADM loads an empty list) .safeParse(rawData); if (!baseBundleValidationResults.success) { From c620a41718a18039afa04f05624a42e48997fbcb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:31:14 -0400 Subject: [PATCH 16/39] test: add unit tests for all example code and code cells embedded in markdown docs --- test/documentation/README.test.ts | 202 ++++++++++++++++++++ test/documentation/USAGE.test.ts | 249 +++++++++++++++++++++++++ test/documentation/first-query.test.ts | 185 ++++++++++++++++++ 3 files changed, 636 insertions(+) create mode 100644 test/documentation/README.test.ts create mode 100644 test/documentation/USAGE.test.ts create mode 100644 test/documentation/first-query.test.ts diff --git a/test/documentation/README.test.ts b/test/documentation/README.test.ts new file mode 100644 index 00000000..7b9754d7 --- /dev/null +++ b/test/documentation/README.test.ts @@ -0,0 +1,202 @@ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { tacticSchema } from '../../src/schemas/index.js'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('README.md Code Examples', () => { + describe('Installation Examples', () => { + it('should validate npm install command format', () => { + // Maps to: README.md - "Installation" section + // Code block: ```bash npm install @mitre-attack/attack-data-model + const packageName = '@mitre-attack/attack-data-model'; + expect(packageName).toMatch(/^@[a-z-]+\/[a-z-]+$/); + }); + + it('should validate version compatibility patterns', () => { + // Maps to: README.md - "Installing Specific Versions" section + // Code blocks: ```bash npm install @mitre-attack/attack-data-model@4.0.0 + const versions = ['4.0.0', '^4.0.0', '~4.0.0', '5.x', '17.1']; + versions.forEach(version => { + expect(version).toMatch(/^[~^]?\d+(\.\d+)?(\.\d+)?$|\d+\.x$/); + }); + }); + + it('should demonstrate version checking from collection object', () => { + // Maps to: README.md - "How to Check Your ATT&CK Version" section + // Code block: ```json { "type": "x-mitre-collection", "x_mitre_attack_spec_version": "3.2.0" } + const collections = globalThis.attackData.objectsByType['x-mitre-collection'] || []; + + if (collections.length > 0) { + const collection = collections[0]; + expect(collection.x_mitre_attack_spec_version).toBeDefined(); + expect(collection.name).toBeDefined(); + } + }); + }); + + describe('Recommended Approach Example', () => { + it('should work with the loading example from README', () => { + // Maps to: README.md - "Recommended Approach" section + // Code block: ```javascript const dataSource = new DataSourceRegistration({ source: 'attack', ... }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'strict' + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('17.1'); + expect(dataSource.options.parsingMode).toBe('strict'); + }); + }); + + describe('Basic Usage Examples', () => { + it('should work with the async function example', () => { + // Maps to: README.md - "Basic Usage" section + // Code block: ```typescript const dataSource = new DataSourceRegistration({ ..., parsingMode: 'relaxed' }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('15.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + + it('should validate that real ATT&CK objects have documented structure', () => { + // Maps to: README.md - "Basic Usage" section + // Code patterns: accessing techniques, tactics, technique properties + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + const tactics = globalThis.attackData.objectsByType['x-mitre-tactic'] || []; + + expect(techniques.length).toBeGreaterThan(100); + expect(tactics.length).toBeGreaterThan(5); + + if (techniques.length > 0) { + const technique = techniques[0]; + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + expect(technique.external_references[0]?.external_id).toBeDefined(); + } + }); + + it('should demonstrate subtechnique patterns', () => { + // Maps to: README.md - "Basic Usage" section + // Code pattern: if (technique.x_mitre_is_subtechnique) { console.log(technique.getParentTechnique()); } + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + const parentTechnique = techniques.find(t => + 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === false + ); + + if (parentTechnique) { + expect(parentTechnique.x_mitre_is_subtechnique).toBe(false); + } + + const subtechnique = techniques.find(t => + 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === true + ); + + if (subtechnique) { + expect(subtechnique.x_mitre_is_subtechnique).toBe(true); + } + }); + + it('should demonstrate accessing attack objects by type', () => { + // Maps to: README.md - "Basic Usage" section + // Code pattern: accessing attackDataModel.techniques, attackDataModel.tactics, etc. + expect(globalThis.attackData.objectsByType['attack-pattern']).toBeDefined(); + expect(globalThis.attackData.objectsByType['x-mitre-tactic']).toBeDefined(); + expect(globalThis.attackData.objectsByType['intrusion-set']).toBeDefined(); + expect(globalThis.attackData.objectsByType['malware']).toBeDefined(); + expect(globalThis.attackData.objectsByType['tool']).toBeDefined(); + expect(globalThis.attackData.objectsByType['course-of-action']).toBeDefined(); + expect(globalThis.attackData.objectsByType['campaign']).toBeDefined(); + }); + }); + + describe('Parsing and Validating a Tactic Examples', () => { + it('should parse valid tactic as shown in README', () => { + // Maps to: README.md - "Parsing and Validating a Tactic" section + // Code block: ```typescript import { tacticSchema } from "@mitre-attack/attack-data-model"; + const validTactic = createSyntheticStixObject('x-mitre-tactic'); + + expect(() => { + const parsedTactic = tacticSchema.parse(validTactic); + expect(parsedTactic.name).toBeDefined(); + expect(parsedTactic.x_mitre_shortname).toBeDefined(); + }).not.toThrow(); + }); + + it('should validate that real tactic objects match README examples', () => { + // Maps to: README.md - "Parsing and Validating a Tactic" section + // Validates that real objects have the structure shown in examples + const tactics = globalThis.attackData.objectsByType['x-mitre-tactic'] || []; + + if (tactics.length > 0) { + const tactic = tactics[0]; + + expect(tactic.id).toBeDefined(); + expect(tactic.type).toBe('x-mitre-tactic'); + expect(tactic.name).toBeDefined(); + expect(tactic.x_mitre_shortname).toBeDefined(); + expect(tactic.external_references).toBeDefined(); + } + }); + }); + + describe('Handling Invalid Data Examples', () => { + it('should handle invalid tactic data as shown in README', () => { + // Maps to: README.md - "Handling Invalid Data" section + // Code block: ```typescript const invalidTactic = { id: "...", type: "x-mitre-tactic" }; + const invalidTactic = { + id: "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", + type: "x-mitre-tactic", + // Missing required fields like name, description, etc. + }; + + expect(() => { + tacticSchema.parse(invalidTactic); + }).toThrow(z.ZodError); + }); + }); + + describe('How It Works Examples', () => { + it('should demonstrate relationship processing patterns', () => { + // Maps to: README.md - "How It Works" section, step 5 + // Text: "automatically processes all 'relationship' objects in the dataset" + const relationships = globalThis.attackData.sros || []; + + expect(relationships.length).toBeGreaterThan(0); + + const usesRelationship = relationships.find(rel => rel.relationship_type === 'uses'); + + if (usesRelationship) { + expect(usesRelationship.source_ref).toBeDefined(); + expect(usesRelationship.target_ref).toBeDefined(); + expect(usesRelationship.relationship_type).toBe('uses'); + } + }); + }); + + describe('AttackDataModel Constructor Examples', () => { + it('should demonstrate AttackDataModel instantiation', () => { + // Maps to: README.md - Various sections showing AttackDataModel usage + // Code pattern: new AttackDataModel(uuid, objects) constructor usage + const uuid = "test-unique-id"; + const attackObjects: any[] = []; + const testDataModel = new AttackDataModel(uuid, attackObjects); + + expect(testDataModel.getUuid()).toBe(uuid); + expect(typeof testDataModel.getUuid()).toBe('string'); + }); + }); +}); \ No newline at end of file diff --git a/test/documentation/USAGE.test.ts b/test/documentation/USAGE.test.ts new file mode 100644 index 00000000..b14a6451 --- /dev/null +++ b/test/documentation/USAGE.test.ts @@ -0,0 +1,249 @@ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { + tacticSchema, + campaignSchema, + techniqueSchema +} from '../../src/schemas/index.js'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('docs/USAGE.md Code Examples', () => { + describe('Module Format Support Examples', () => { + it('should work with ESM example', () => { + // Maps to: docs/USAGE.md - "ESM Usage Example" section + // Code block: ```javascript import { AttackDataModel } from '@mitre-attack/attack-data-model'; + const uuid = "my-unique-id"; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.getUuid()).toBe("my-unique-id"); + }); + + it('should work with CommonJS pattern', () => { + // Maps to: docs/USAGE.md - "CommonJS Usage Example" section + // Code block: ```javascript const { AttackDataModel } = require('@mitre-attack/attack-data-model'); + const uuid = "my-unique-id"; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.getUuid()).toBe("my-unique-id"); + }); + }); + + describe('Package Structure Examples', () => { + it('should support the hierarchical export pattern', () => { + // Maps to: docs/USAGE.md - "Hierarchical Structure" section + // Code block: ```typescript export * from './classes/index.js'; + expect(tacticSchema).toBeDefined(); + expect(campaignSchema).toBeDefined(); + expect(techniqueSchema).toBeDefined(); + expect(AttackDataModel).toBeDefined(); + expect(DataSourceRegistration).toBeDefined(); + }); + }); + + describe('Using the Schemas Examples', () => { + it('should support schema import patterns', () => { + // Maps to: docs/USAGE.md - "Accessing Schemas" section + // Code block: ```typescript import { campaignSchema } from '@mitre-attack/attack-data-model'; + const validCampaign = createSyntheticStixObject('campaign'); + + expect(() => { + const parsedCampaign = campaignSchema.parse(validCampaign); + expect(parsedCampaign.name).toBeDefined(); + }).not.toThrow(); + }); + + it('should demonstrate schema extension patterns', () => { + // Maps to: docs/USAGE.md - Schema extension section + // Code block: ```typescript const myCustomCampaignSchema = campaignSchema.extend({ /* additional fields */ }); + // Note: We can't test the exact extend example due to refinement issues mentioned in docs + // Instead, we test that the base schema works as expected + const campaign = createSyntheticStixObject('campaign'); + + expect(() => { + const parsed = campaignSchema.parse(campaign); + expect(parsed).toBeDefined(); + }).not.toThrow(); + }); + + it('should demonstrate refinement functionality', () => { + // Maps to: docs/USAGE.md - "Schema refinements" section + // Text: "validating that the first reference in external_references contains a valid ATT&CK ID" + const technique = createSyntheticStixObject('attack-pattern'); + + expect(technique?.external_references).toBeDefined(); + expect(technique?.external_references?.[0]).toBeDefined(); + expect(technique?.external_references?.[0].source_name).toBe('mitre-attack'); + expect(technique?.external_references?.[0].external_id).toBeDefined(); + + expect(() => { + techniqueSchema.parse(technique); + }).not.toThrow(); + }); + }); + + describe('Validating Data Examples', () => { + it('should validate raw tactic data as shown in USAGE', () => { + // Maps to: docs/USAGE.md - "Validating Data" section + // Code block: ```typescript import { tacticSchema } from '@mitre-attack/attack-data-model'; + const validTactic = createSyntheticStixObject('x-mitre-tactic'); + + expect(() => { + const parsedTactic = tacticSchema.parse(validTactic); + expect(parsedTactic.name).toBeDefined(); + expect(parsedTactic.x_mitre_shortname).toBeDefined(); + }).not.toThrow(); + }); + }); + + describe('TypeScript Types Examples', () => { + it('should support TypeScript type patterns', () => { + // Maps to: docs/USAGE.md - "TypeScript Types" section + // Code block: ```typescript import type { Tactic } from '@mitre-attack/attack-data-model'; + // Test that type imports work correctly (validated at compile time) + expect(tacticSchema).toBeDefined(); + expect(campaignSchema).toBeDefined(); + expect(techniqueSchema).toBeDefined(); + }); + }); + + describe('Schema Hierarchy Examples', () => { + it('should support schema hierarchy as described', () => { + // Maps to: docs/USAGE.md - "Schema Hierarchy" section + // Validates the hierarchical structure: STIX base → ATT&CK base → specific objects + const validTechnique = createSyntheticStixObject('attack-pattern'); + const parsed = techniqueSchema.parse(validTechnique); + + // Should have STIX base properties + expect(parsed.id).toBeDefined(); + expect(parsed.type).toBe('attack-pattern'); + expect(parsed.spec_version).toBeDefined(); + + // Should have ATT&CK-specific properties + expect(parsed.x_mitre_domains).toBeDefined(); + expect(parsed.x_mitre_attack_spec_version).toBeDefined(); + }); + }); + + describe('AttackDataModel Class Examples', () => { + it('should demonstrate AttackDataModel basic usage', () => { + // Maps to: docs/USAGE.md - "AttackDataModel Class" section + // Code block: ```typescript const attackDataModel = new AttackDataModel(); + const uuid = "test-uuid"; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.techniques).toBeDefined(); + expect(Array.isArray(attackDataModel.techniques)).toBe(true); + expect(attackDataModel.campaigns).toBeDefined(); + expect(Array.isArray(attackDataModel.campaigns)).toBe(true); + }); + }); + + describe('Initializing with Data Examples', () => { + it('should support DataSource configuration patterns', () => { + // Maps to: docs/USAGE.md - "Initializing with Data" section + // Code block: ```typescript const dataSource = new DataSource({ source: 'attack', ... }); + // Note: Using DataSourceRegistration instead of DataSource as shown in examples + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('15.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); + + describe('Relationship Mapping Examples', () => { + it('should validate campaign relationship navigation patterns', () => { + // Maps to: docs/USAGE.md - "Relationship Mapping" section + // Code block: ```typescript const techniques = campaign.getTechniques(); + // Test using real data to validate relationship patterns exist + const campaigns = globalThis.attackData.objectsByType['campaign'] || []; + + if (campaigns.length > 0) { + const campaign = campaigns[0]; + + // Properties mentioned in the relationship mapping examples + expect(campaign.name).toBeDefined(); + expect(campaign.id).toBeDefined(); + expect(campaign.type).toBe('campaign'); + } + }); + }); + + describe('Examples Section Code Blocks', () => { + it('should demonstrate technique-tactic navigation', () => { + // Maps to: docs/USAGE.md - "Examples" section + // Code block: ```typescript techniques.forEach((technique) => { const tactics = technique.getTactics(); }); + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + if (techniques.length > 0) { + const technique = techniques[0]; + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + } + }); + + it('should demonstrate software-group relationships', () => { + // Maps to: docs/USAGE.md - "Working with Software and Groups" section + // Code block: ```typescript malwareList.forEach((malware) => { const associatedGroups = malware.getAssociatedGroups(); }); + const malware = globalThis.attackData.objectsByType['malware'] || []; + + if (malware.length > 0) { + const malwareItem = malware[0]; + expect(malwareItem.name).toBeDefined(); + expect(malwareItem.type).toBe('malware'); + } + }); + + it('should demonstrate technique validation patterns', () => { + // Maps to: docs/USAGE.md - "Validating and Parsing a Technique" section + // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; + const rawTechniqueData = createSyntheticStixObject('attack-pattern'); + + expect(() => { + const technique = techniqueSchema.parse(rawTechniqueData); + expect(technique.name).toBeDefined(); + }).not.toThrow(); + }); + + it('should handle invalid technique data', () => { + // Maps to: docs/USAGE.md - "Handling Invalid Data" section + // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; + const invalidTechniqueData = { + id: "attack-pattern--1234abcd-5678-efgh-ijkl-9876mnopqrst", + type: "attack-pattern", + // Missing required fields + }; + + expect(() => { + techniqueSchema.parse(invalidTechniqueData); + }).toThrow(z.ZodError); + }); + }); + + describe('Data Sources Examples', () => { + it('should support data source patterns mentioned in USAGE', () => { + // Maps to: docs/USAGE.md - "Data Sources" section + // Text: "Loading Data from the ATT&CK GitHub Repository" + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); +}); \ No newline at end of file diff --git a/test/documentation/first-query.test.ts b/test/documentation/first-query.test.ts new file mode 100644 index 00000000..1fc042b6 --- /dev/null +++ b/test/documentation/first-query.test.ts @@ -0,0 +1,185 @@ +import { describe, it, expect } from 'vitest'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('examples/first-query.ts Code Example', () => { + describe('DataSourceRegistration Configuration', () => { + it('should use the exact configuration from the example', () => { + // Maps to: examples/first-query.ts - lines 7-12 + // Code: const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', version: '17.1', parsingMode: 'relaxed' }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('17.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); + + describe('AttackDataModel Usage Patterns', () => { + it('should validate AttackDataModel methods used in the example', () => { + // Maps to: examples/first-query.ts - lines 22-31 + // Code: const attackDataModel = loadDataModel(uuid); attackDataModel.techniques.length; attackDataModel.techniques.slice(0, 5) + const uuid = "test-uuid"; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + // The example uses these properties/methods + expect(attackDataModel.techniques).toBeDefined(); + expect(Array.isArray(attackDataModel.techniques)).toBe(true); + expect(typeof attackDataModel.techniques.length).toBe('number'); + + // The example calls .slice(0, 5) on techniques array + expect(typeof attackDataModel.techniques.slice).toBe('function'); + + // Test that slice works as expected in the example + const sliced = attackDataModel.techniques.slice(0, 5); + expect(Array.isArray(sliced)).toBe(true); + expect(sliced.length).toBeLessThanOrEqual(5); + }); + }); + + describe('Technique Data Structure Validation', () => { + it('should validate that real techniques have properties accessed in the example', () => { + // Maps to: examples/first-query.ts - line 29 + // Code: technique.name and technique.external_references[0].external_id + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + if (techniques.length > 0) { + const technique = techniques[0]; + + // Properties accessed in the example + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + expect(Array.isArray(technique.external_references)).toBe(true); + + if (technique.external_references.length > 0) { + expect(technique.external_references[0].external_id).toBeDefined(); + + // The example formats output as: `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})` + const formattedOutput = `1. ${technique.name} (${technique.external_references[0].external_id})`; + expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); + } + } + }); + + it('should validate the example output format pattern', () => { + // Maps to: examples/first-query.ts - line 28-31 + // Code: forEach((technique, index) => { console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); }); + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + // Test the first 5 techniques as the example does + const first5Techniques = techniques.slice(0, 5); + + first5Techniques.forEach((technique, index) => { + expect(technique.name).toBeDefined(); + expect(technique.external_references?.[0]?.external_id).toBeDefined(); + + // Validate the exact output format from the example + const formattedOutput = `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`; + expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); + expect(formattedOutput).toContain(technique.name); + expect(formattedOutput).toContain(technique.external_references[0].external_id); + }); + }); + }); + + describe('Async Function Pattern Validation', () => { + it('should validate the async function structure used in the example', () => { + // Maps to: examples/first-query.ts - lines 3-38 + // Code: async function exploreAttackData() { try { const uuid = await registerDataSource(dataSource); } catch (error) { ... } } + + // Test that the async pattern components work + const testAsyncPattern = async () => { + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + // The example checks these properties exist + expect(dataSource.options.source).toBeDefined(); + expect(dataSource.options.domain).toBeDefined(); + expect(dataSource.options.version).toBeDefined(); + expect(dataSource.options.parsingMode).toBeDefined(); + + return dataSource; + }; + + expect(testAsyncPattern).not.toThrow(); + }); + }); + + describe('Console Output Patterns', () => { + it('should validate console output message formats from the example', () => { + // Maps to: examples/first-query.ts - lines 4, 19, 23, 26 + // Code: console.log('🎯 Loading ATT&CK Enterprise data...\n'); console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); + + const techniqueCount = globalThis.attackData.objectsByType['attack-pattern']?.length || 0; + + // Test the loading message format from the example + const loadingMessage = `Loaded ${techniqueCount} techniques`; + expect(loadingMessage).toMatch(/^Loaded \d+ techniques$/); + expect(techniqueCount).toBeGreaterThan(0); + + // Test the exploration message format + const explorationMessage = 'First 5 techniques:'; + expect(explorationMessage).toBe('First 5 techniques:'); + }); + }); + + describe('Error Handling Pattern', () => { + it('should validate error handling structure from the example', () => { + // Maps to: examples/first-query.ts - lines 32-37 + // Code: } else { console.error('❌ Failed to register data source'); } } catch (error) { console.error('❌ Error:', error); } + + // Test that the error handling patterns would work + const testErrorHandling = () => { + try { + // Simulate the uuid check pattern from the example + const uuid = "test-uuid"; // In real example, this comes from registerDataSource + + if (uuid) { + expect(uuid).toBeDefined(); + expect(typeof uuid).toBe('string'); + } else { + // This would match the error case in the example + expect(uuid).toBeNull(); + } + } catch (error) { + // Error handling as shown in the example + expect(error).toBeDefined(); + } + }; + + expect(testErrorHandling).not.toThrow(); + }); + }); + + describe('Import Statement Validation', () => { + it('should validate the imports used in the example', () => { + // Maps to: examples/first-query.ts - line 1 + // Code: import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + + // Test that the imported classes/functions exist and are usable + expect(DataSourceRegistration).toBeDefined(); + expect(typeof DataSourceRegistration).toBe('function'); + + // Test that DataSourceRegistration can be instantiated as shown in the example + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + expect(dataSource).toBeInstanceOf(DataSourceRegistration); + }); + }); +}); \ No newline at end of file From 89ad468963cc4874f59185b20b1457b69d8e1f15 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:39:15 -0400 Subject: [PATCH 17/39] feat(stix-bundle.schema): add meta description to stixBundleSchema --- src/schemas/sdo/stix-bundle.schema.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 2eb8c09c..fa7d702d 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -181,6 +181,9 @@ export const stixBundleSchema = z type: createStixTypeValidator('bundle'), objects: attackObjectsSchema, }) + .meta({ + description: 'A Bundle is a collection of arbitrary STIX Objects grouped together in a single container. A Bundle does not have any semantic meaning and the objects contained within the Bundle are not considered related by virtue of being in the same Bundle. A STIX Bundle Object is not a STIX Object but makes use of the type and id Common Properties.' + }) .strict() .check((ctx) => { createFirstBundleObjectRefinement()(ctx); From 2a23197d4c5176bbc843a38e9065916b33f607cf Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:41:13 -0400 Subject: [PATCH 18/39] feat(tactic.schema): add meta description to tacticSchema --- src/schemas/sdo/tactic.schema.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/schemas/sdo/tactic.schema.ts b/src/schemas/sdo/tactic.schema.ts index 3dbe8e9e..af338ce6 100644 --- a/src/schemas/sdo/tactic.schema.ts +++ b/src/schemas/sdo/tactic.schema.ts @@ -91,6 +91,10 @@ export const tacticSchema = attackBaseDomainObjectSchema x_mitre_contributors: xMitreContributorsSchema.optional(), }) + .meta({ + description: + "Tactics represent the adversary's tactical goals during an attack and are defined by `x-mitre-tactic` objects. As custom STIX types, they extend the generic STIX Domain Object pattern.", + }) .required({ created_by_ref: true, // Optional in STIX but required in ATT&CK object_marking_refs: true, // Optional in STIX but required in ATT&CK From 5ac1739b8aade152000b2db997d94fe29f6931f9 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 15 Aug 2025 12:43:35 -0500 Subject: [PATCH 19/39] chore: update contributor list --- package.json | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index ea3568a8..4eb3985d 100644 --- a/package.json +++ b/package.json @@ -48,29 +48,9 @@ }, "contributors": [ { - "name": "Sean Sica", - "email": "ssica@mitre.org", - "url": "https://github.com/seansica" - }, - { - "name": "Charissa Miller", - "email": "clmiller@mitre.org", - "url": "https://github.com/clemiller" - }, - { - "name": "Anjali Pare", - "email": "apare@mitre.org", - "url": "https://github.com/adpare" - }, - { - "name": "Erin Hall", - "email": "halle@mitre.org", - "url": "https://github.com/erinehall" - }, - { - "name": "Jared Ondricek", - "email": "jondricek@mitre.org", - "url": "https://github.com/jondricek" + "name": "MITRE ATT&CK", + "email": "attack@mitre.org", + "url": "https://github.com/mitre-attack" } ], "files": [ From f790d237a2d813433cf73d903025b66e8bb64107 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 15 Aug 2025 13:05:55 -0500 Subject: [PATCH 20/39] chore: move scripts, lint them, and update eslint config --- eslint.config.js | 22 +- .../validate-local-stix21-bundle.ts | 186 ++++++++------- .../validate-prod-stix21-bundles.ts | 211 +++++++++++------- 3 files changed, 251 insertions(+), 168 deletions(-) rename {scripts => examples/validate-stix}/validate-local-stix21-bundle.ts (84%) rename {scripts => examples/validate-stix}/validate-prod-stix21-bundles.ts (86%) diff --git a/eslint.config.js b/eslint.config.js index d5fe1a02..feb2ba41 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,15 @@ -import globals from "globals"; -import pluginJs from "@eslint/js"; -import tseslint from "typescript-eslint"; +import pluginJs from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; - +import globals from 'globals'; +import tseslint from 'typescript-eslint'; export default [ - { files: ["**/*.{js,mjs,cjs,ts}"] }, - { languageOptions: { globals: globals.browser } }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - eslintPluginPrettierRecommended -]; \ No newline at end of file + { + ignores: ['node_modules', 'dist', 'docusaurus'], + }, + { files: ['**/*.{js,mjs,cjs,ts}'] }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + eslintPluginPrettierRecommended, +]; diff --git a/scripts/validate-local-stix21-bundle.ts b/examples/validate-stix/validate-local-stix21-bundle.ts similarity index 84% rename from scripts/validate-local-stix21-bundle.ts rename to examples/validate-stix/validate-local-stix21-bundle.ts index d87b5e3c..33b3bbaf 100644 --- a/scripts/validate-local-stix21-bundle.ts +++ b/examples/validate-stix/validate-local-stix21-bundle.ts @@ -38,7 +38,7 @@ import fs from 'fs/promises'; import path from 'path'; import { z } from 'zod'; -import { stixBundleSchema, type StixBundle } from '../src/schemas/sdo/stix-bundle.schema'; +import { stixBundleSchema, type StixBundle } from '@mitre-attack/attack-data-model'; /** * Formats a ZodError into a readable string with context about the failing objects @@ -147,24 +147,28 @@ function formatZodError(error: z.ZodError, bundle: StixBundle): string { function formatError(issue: z.ZodIssue): string { // For enum validation errors, reformat to include the received value clearly if (issue.code === 'invalid_enum_value' && issue.received) { - return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map(opt => `'${opt}'`).join(' | ')}`; + return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map((opt) => `'${opt}'`).join(' | ')}`; } - + // For invalid arguments, include value clearly if (issue.code === 'invalid_arguments' && issue.argumentsError) { return `Invalid arguments: ${issue.message}`; } // For custom validation errors that contain 'Expected X, received Y' format - if (issue.code === 'custom' && issue.message.includes('Expected') && issue.message.includes('received')) { + if ( + issue.code === 'custom' && + issue.message.includes('Expected') && + issue.message.includes('received') + ) { return issue.message; } - + // Handle invalid types more clearly if (issue.code === 'invalid_type') { return `Invalid type. Expected ${issue.expected}, received ${issue.received}`; } - + // Return the original message for other types of errors return issue.message; } @@ -174,40 +178,58 @@ function formatError(issue: z.ZodIssue): string { * @returns Structured data about validation errors for analysis */ function collectErrorStatistics( - error: z.ZodError, - bundle: StixBundle + error: z.ZodError, + bundle: StixBundle, ): { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; } { - const invalidEnumValues = new Map(); + const invalidEnumValues = new Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >(); const errorsByPath = new Map(); const errorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const errorsByType: Record = {}; error.issues.forEach((issue) => { // Get object information if this is an object-level error - let objectId = "unknown"; - let objectName = "unknown"; - let objectType = "unknown"; - let objectStatus = "unknown"; - + let objectId = 'unknown'; + let objectName = 'unknown'; + let objectType = 'unknown'; + let objectStatus = 'unknown'; + const objectsIndex = issue.path.findIndex((segment) => segment === 'objects'); if (objectsIndex !== -1 && objectsIndex + 1 < issue.path.length) { const objectIndex = issue.path[objectsIndex + 1]; if (typeof objectIndex === 'number' && objectIndex < bundle.objects.length) { const errorObject = bundle.objects[objectIndex]; - + objectId = errorObject.id; objectType = errorObject.type; objectName = (errorObject as any).name || 'Unnamed'; - + // Determine object status objectStatus = 'Active'; if ((errorObject as any).x_mitre_deprecated) { @@ -215,10 +237,10 @@ function collectErrorStatistics( } else if ('revoked' in errorObject && (errorObject as any).revoked) { objectStatus = 'Revoked'; } - + // Count by status errorsByStatus[objectStatus] = (errorsByStatus[objectStatus] || 0) + 1; - + // Count by type errorsByType[objectType] = (errorsByType[objectType] || 0) + 1; } @@ -230,13 +252,13 @@ function collectErrorStatistics( if (!invalidEnumValues.has(pathKey)) { invalidEnumValues.set(pathKey, []); } - + invalidEnumValues.get(pathKey)?.push({ value: String(issue.received), objectId, objectName, objectType, - objectStatus + objectStatus, }); } @@ -253,120 +275,137 @@ function collectErrorStatistics( */ function formatErrorAggregateReport( stats: { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; }, totalErrorCount: number, - bundleName: string + bundleName: string, ): string { const report: string[] = []; - + report.push(`\n=== ERROR AGGREGATION REPORT FOR ${bundleName} ===`); report.push(`Total validation issues: ${totalErrorCount}\n`); - + // Report errors by status report.push(`Errors by object status:`); for (const [status, count] of Object.entries(stats.errorsByStatus)) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${status}: ${count} (${percentage}%)`); } report.push(''); - + // Report errors by type report.push(`Errors by object type:`); - const sortedTypeEntries = Object.entries(stats.errorsByType) - .sort(([, countA], [, countB]) => countB - countA); - + const sortedTypeEntries = Object.entries(stats.errorsByType).sort( + ([, countA], [, countB]) => countB - countA, + ); + for (const [type, count] of sortedTypeEntries) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${type}: ${count} (${percentage}%)`); } report.push(''); - + // Report top error paths report.push(`Top error paths:`); const sortedPathEntries = [...stats.errorsByPath.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 10); - + for (const [path, count] of sortedPathEntries) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${path}: ${count} (${percentage}%)`); } report.push(''); - + // Report invalid enum values report.push(`Invalid enum values by property:`); - + // Group by property first - const propertiesByEnumValue = new Map(); - + const propertiesByEnumValue = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] } + >(); + for (const [path, values] of stats.invalidEnumValues.entries()) { // Extract the property name from the path const pathParts = path.split('.'); const propertyName = pathParts.slice(2).join('.'); - + for (const valueInfo of values) { const valueKey = `${propertyName}|${valueInfo.value}`; - + if (!propertiesByEnumValue.has(valueKey)) { propertiesByEnumValue.set(valueKey, { value: valueInfo.value, - objects: [] + objects: [], }); } - + // Only add this object if it's not already in the list const existingObjects = propertiesByEnumValue.get(valueKey)!.objects; - if (!existingObjects.some(obj => obj.id === valueInfo.objectId)) { + if (!existingObjects.some((obj) => obj.id === valueInfo.objectId)) { existingObjects.push({ id: valueInfo.objectId, name: valueInfo.objectName, type: valueInfo.objectType, - status: valueInfo.objectStatus + status: valueInfo.objectStatus, }); } } } - + // Group properties by their name - const groupedProperties = new Map(); - + const groupedProperties = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] }[] + >(); + for (const [key, valueInfo] of propertiesByEnumValue.entries()) { const propertyName = key.split('|')[0]; - + if (!groupedProperties.has(propertyName)) { groupedProperties.set(propertyName, []); } - + groupedProperties.get(propertyName)!.push(valueInfo); } - + // Output the grouped invalid enum values for (const [propertyName, valueInfos] of groupedProperties.entries()) { report.push(` Property: ${propertyName}`); - + for (const valueInfo of valueInfos) { report.push(` Invalid value: '${valueInfo.value}'`); report.push(` Found in ${valueInfo.objects.length} objects:`); - + // Sort objects by name for more consistent output const sortedObjects = [...valueInfo.objects].sort((a, b) => a.name.localeCompare(b.name)); - - for (const obj of sortedObjects.slice(0, 10)) { // Limit to first 10 objects for brevity + + for (const obj of sortedObjects.slice(0, 10)) { + // Limit to first 10 objects for brevity report.push(` - ${obj.name} (${obj.id}) [${obj.type}, ${obj.status}]`); } - + if (sortedObjects.length > 10) { report.push(` ... and ${sortedObjects.length - 10} more objects`); } - + report.push(''); } } - + return report.join('\n'); } @@ -452,7 +491,9 @@ async function validateLocalStixBundles() { const filePath = fileArg.split('=')[1]; if (!filePath) { - console.error('Error: Invalid file path. Please provide a valid path to a STIX bundle JSON file.'); + console.error( + 'Error: Invalid file path. Please provide a valid path to a STIX bundle JSON file.', + ); process.exit(1); } @@ -474,12 +515,8 @@ async function validateLocalStixBundles() { // Generate error statistics report if there's an error if (result.error) { const stats = collectErrorStatistics(result.error, result.bundle); - const statsReport = formatErrorAggregateReport( - stats, - result.errorCount, - result.fileName - ); - + const statsReport = formatErrorAggregateReport(stats, result.errorCount, result.fileName); + await fs.writeFile(statsFilePath, statsReport); console.log(`Aggregate error statistics written to: ${statsFilePath}`); console.log(statsReport); // Also print to console @@ -507,7 +544,6 @@ async function validateLocalStixBundles() { .forEach(([type, count]) => { console.log(` ${type}: ${count}`); }); - } catch (error) { console.error('Error validating STIX bundle:', error); process.exit(1); @@ -515,4 +551,4 @@ async function validateLocalStixBundles() { } // Run the validation -validateLocalStixBundles().catch(console.error); \ No newline at end of file +validateLocalStixBundles().catch(console.error); diff --git a/scripts/validate-prod-stix21-bundles.ts b/examples/validate-stix/validate-prod-stix21-bundles.ts similarity index 86% rename from scripts/validate-prod-stix21-bundles.ts rename to examples/validate-stix/validate-prod-stix21-bundles.ts index 5fbe7573..537f4e20 100644 --- a/scripts/validate-prod-stix21-bundles.ts +++ b/examples/validate-stix/validate-prod-stix21-bundles.ts @@ -38,8 +38,12 @@ import axios from 'axios'; import fs from 'fs/promises'; import { z } from 'zod'; -import { stixBundleSchema, type StixBundle } from '../src/schemas/sdo/stix-bundle.schema'; -import { type AttackDomain, attackDomainSchema } from '../src/schemas/common'; +import { + attackDomainSchema, + stixBundleSchema, + type AttackDomain, + type StixBundle, +} from '@mitre-attack/attack-data-model'; const SUPPORTED_DOMAINS = attackDomainSchema.options; @@ -150,24 +154,28 @@ function formatZodError(error: z.ZodError, bundle: StixBundle): string { function formatError(issue: z.ZodIssue): string { // For enum validation errors, reformat to include the received value clearly if (issue.code === 'invalid_enum_value' && issue.received) { - return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map(opt => `'${opt}'`).join(' | ')}`; + return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map((opt) => `'${opt}'`).join(' | ')}`; } - + // For invalid arguments, include value clearly if (issue.code === 'invalid_arguments' && issue.argumentsError) { return `Invalid arguments: ${issue.message}`; } // For custom validation errors that contain 'Expected X, received Y' format - if (issue.code === 'custom' && issue.message.includes('Expected') && issue.message.includes('received')) { + if ( + issue.code === 'custom' && + issue.message.includes('Expected') && + issue.message.includes('received') + ) { return issue.message; } - + // Handle invalid types more clearly if (issue.code === 'invalid_type') { return `Invalid type. Expected ${issue.expected}, received ${issue.received}`; } - + // Return the original message for other types of errors return issue.message; } @@ -244,40 +252,58 @@ async function validateStix21Bundle(domain: AttackDomain): Promise<{ * @returns Structured data about validation errors for analysis */ function collectErrorStatistics( - error: z.ZodError, - bundle: StixBundle + error: z.ZodError, + bundle: StixBundle, ): { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; } { - const invalidEnumValues = new Map(); + const invalidEnumValues = new Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >(); const errorsByPath = new Map(); const errorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const errorsByType: Record = {}; error.issues.forEach((issue) => { // Get object information if this is an object-level error - let objectId = "unknown"; - let objectName = "unknown"; - let objectType = "unknown"; - let objectStatus = "unknown"; - + let objectId = 'unknown'; + let objectName = 'unknown'; + let objectType = 'unknown'; + let objectStatus = 'unknown'; + const objectsIndex = issue.path.findIndex((segment) => segment === 'objects'); if (objectsIndex !== -1 && objectsIndex + 1 < issue.path.length) { const objectIndex = issue.path[objectsIndex + 1]; if (typeof objectIndex === 'number' && objectIndex < bundle.objects.length) { const errorObject = bundle.objects[objectIndex]; - + objectId = errorObject.id; objectType = errorObject.type; objectName = (errorObject as any).name || 'Unnamed'; - + // Determine object status objectStatus = 'Active'; if ((errorObject as any).x_mitre_deprecated) { @@ -285,10 +311,10 @@ function collectErrorStatistics( } else if ('revoked' in errorObject && (errorObject as any).revoked) { objectStatus = 'Revoked'; } - + // Count by status errorsByStatus[objectStatus] = (errorsByStatus[objectStatus] || 0) + 1; - + // Count by type errorsByType[objectType] = (errorsByType[objectType] || 0) + 1; } @@ -300,13 +326,13 @@ function collectErrorStatistics( if (!invalidEnumValues.has(pathKey)) { invalidEnumValues.set(pathKey, []); } - + invalidEnumValues.get(pathKey)?.push({ value: String(issue.received), objectId, objectName, objectType, - objectStatus + objectStatus, }); } @@ -323,119 +349,136 @@ function collectErrorStatistics( */ function formatErrorAggregateReport( stats: { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; }, - totalErrorCount: number + totalErrorCount: number, ): string { const report: string[] = []; - + report.push(`\n=== ERROR AGGREGATION REPORT ===`); report.push(`Total validation issues: ${totalErrorCount}\n`); - + // Report errors by status report.push(`Errors by object status:`); for (const [status, count] of Object.entries(stats.errorsByStatus)) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${status}: ${count} (${percentage}%)`); } report.push(''); - + // Report errors by type report.push(`Errors by object type:`); - const sortedTypeEntries = Object.entries(stats.errorsByType) - .sort(([, countA], [, countB]) => countB - countA); - + const sortedTypeEntries = Object.entries(stats.errorsByType).sort( + ([, countA], [, countB]) => countB - countA, + ); + for (const [type, count] of sortedTypeEntries) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${type}: ${count} (${percentage}%)`); } report.push(''); - + // Report top error paths report.push(`Top error paths:`); const sortedPathEntries = [...stats.errorsByPath.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 10); - + for (const [path, count] of sortedPathEntries) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${path}: ${count} (${percentage}%)`); } report.push(''); - + // Report invalid enum values report.push(`Invalid enum values by property:`); - + // Group by property first - const propertiesByEnumValue = new Map(); - + const propertiesByEnumValue = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] } + >(); + for (const [path, values] of stats.invalidEnumValues.entries()) { // Extract the property name from the path const pathParts = path.split('.'); const propertyName = pathParts.slice(2).join('.'); - + for (const valueInfo of values) { const valueKey = `${propertyName}|${valueInfo.value}`; - + if (!propertiesByEnumValue.has(valueKey)) { propertiesByEnumValue.set(valueKey, { value: valueInfo.value, - objects: [] + objects: [], }); } - + // Only add this object if it's not already in the list const existingObjects = propertiesByEnumValue.get(valueKey)!.objects; - if (!existingObjects.some(obj => obj.id === valueInfo.objectId)) { + if (!existingObjects.some((obj) => obj.id === valueInfo.objectId)) { existingObjects.push({ id: valueInfo.objectId, name: valueInfo.objectName, type: valueInfo.objectType, - status: valueInfo.objectStatus + status: valueInfo.objectStatus, }); } } } - + // Group properties by their name - const groupedProperties = new Map(); - + const groupedProperties = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] }[] + >(); + for (const [key, valueInfo] of propertiesByEnumValue.entries()) { const propertyName = key.split('|')[0]; - + if (!groupedProperties.has(propertyName)) { groupedProperties.set(propertyName, []); } - + groupedProperties.get(propertyName)!.push(valueInfo); } - + // Output the grouped invalid enum values for (const [propertyName, valueInfos] of groupedProperties.entries()) { report.push(` Property: ${propertyName}`); - + for (const valueInfo of valueInfos) { report.push(` Invalid value: '${valueInfo.value}'`); report.push(` Found in ${valueInfo.objects.length} objects:`); - + // Sort objects by name for more consistent output const sortedObjects = [...valueInfo.objects].sort((a, b) => a.name.localeCompare(b.name)); - - for (const obj of sortedObjects.slice(0, 10)) { // Limit to first 10 objects for brevity + + for (const obj of sortedObjects.slice(0, 10)) { + // Limit to first 10 objects for brevity report.push(` - ${obj.name} (${obj.id}) [${obj.type}, ${obj.status}]`); } - + if (sortedObjects.length > 10) { report.push(` ... and ${sortedObjects.length - 10} more objects`); } - + report.push(''); } } - + return report.join('\n'); } @@ -466,19 +509,21 @@ async function validateStixBundles() { const statsFilePath = `./validation-stats-${timestamp}.txt`; // Validate each domain - const results = await Promise.all(requestedDomains.map((domain) => validateStix21Bundle(domain))); + const results = await Promise.all( + requestedDomains.map((domain) => validateStix21Bundle(domain)), + ); // Collect all error messages const allErrors: string[] = []; let totalErrorCount = 0; - + // For collecting aggregate error statistics const allInvalidEnumValues = new Map>(); const allErrorsByPath = new Map(); const allErrorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const allErrorsByType: Record = {}; @@ -490,28 +535,28 @@ async function validateStixBundles() { ); allErrors.push(result.formattedError); totalErrorCount += result.errorCount; - + // Use the stored error from validation if (result.error) { // Collect statistics from the domain const domainStats = collectErrorStatistics(result.error, result.bundle); - + // Merge into overall statistics for (const [path, values] of domainStats.invalidEnumValues.entries()) { if (!allInvalidEnumValues.has(path)) { allInvalidEnumValues.set(path, new Set()); } - values.forEach(value => allInvalidEnumValues.get(path)?.add(value)); + values.forEach((value) => allInvalidEnumValues.get(path)?.add(value)); } - + for (const [path, count] of domainStats.errorsByPath.entries()) { allErrorsByPath.set(path, (allErrorsByPath.get(path) || 0) + count); } - + for (const [status, count] of Object.entries(domainStats.errorsByStatus)) { allErrorsByStatus[status] = (allErrorsByStatus[status] || 0) + count; } - + for (const [type, count] of Object.entries(domainStats.errorsByType)) { allErrorsByType[type] = (allErrorsByType[type] || 0) + count; } @@ -523,16 +568,16 @@ async function validateStixBundles() { if (allErrors.length > 0) { await fs.writeFile(errorFilePath, allErrors.join('\n')); console.log(`Full error details written to: ${errorFilePath}`); - + // Generate and write aggregate statistics report const statsReport = formatErrorAggregateReport( - { - invalidEnumValues: allInvalidEnumValues, + { + invalidEnumValues: allInvalidEnumValues, errorsByPath: allErrorsByPath, errorsByStatus: allErrorsByStatus, - errorsByType: allErrorsByType + errorsByType: allErrorsByType, }, - totalErrorCount + totalErrorCount, ); await fs.writeFile(statsFilePath, statsReport); console.log(`Aggregate error statistics written to: ${statsFilePath}`); From ee794bdbb9f7b66f336e878e68c1c912471478f7 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 15 Aug 2025 13:06:57 -0500 Subject: [PATCH 21/39] chore: lint the first query example --- examples/first-query.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/first-query.ts b/examples/first-query.ts index bb72b172..79737082 100644 --- a/examples/first-query.ts +++ b/examples/first-query.ts @@ -1,4 +1,8 @@ -import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; +import { + registerDataSource, + loadDataModel, + DataSourceRegistration, +} from '@mitre-attack/attack-data-model'; async function exploreAttackData() { console.log('🎯 Loading ATT&CK Enterprise data...\n'); From 1ec7f197d9bef3b5feeed1875e8689908485abdf Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 15 Aug 2025 13:11:28 -0500 Subject: [PATCH 22/39] chore: lint the test folder --- test/documentation/README.test.ts | 42 +++++++++++----------- test/documentation/USAGE.test.ts | 48 ++++++++++++------------- test/documentation/first-query.test.ts | 44 +++++++++++------------ test/global.d.ts | 4 +-- test/objects/analytic.test.ts | 4 +-- test/objects/asset.test.ts | 4 +-- test/objects/campaign.test.ts | 4 +-- test/objects/data-component.test.ts | 4 +-- test/objects/data-source.test.ts | 4 +-- test/objects/detection-strategy.test.ts | 15 +++++--- test/objects/group.test.ts | 4 +-- test/objects/log-source.test.ts | 4 +-- test/objects/malware.test.ts | 4 +-- test/objects/mitigation.test.ts | 4 +-- test/objects/relationship.test.ts | 28 +++++++-------- test/objects/technique.test.ts | 4 +-- 16 files changed, 102 insertions(+), 119 deletions(-) diff --git a/test/documentation/README.test.ts b/test/documentation/README.test.ts index 7b9754d7..c991056e 100644 --- a/test/documentation/README.test.ts +++ b/test/documentation/README.test.ts @@ -18,7 +18,7 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "Installing Specific Versions" section // Code blocks: ```bash npm install @mitre-attack/attack-data-model@4.0.0 const versions = ['4.0.0', '^4.0.0', '~4.0.0', '5.x', '17.1']; - versions.forEach(version => { + versions.forEach((version) => { expect(version).toMatch(/^[~^]?\d+(\.\d+)?(\.\d+)?$|\d+\.x$/); }); }); @@ -27,7 +27,7 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "How to Check Your ATT&CK Version" section // Code block: ```json { "type": "x-mitre-collection", "x_mitre_attack_spec_version": "3.2.0" } const collections = globalThis.attackData.objectsByType['x-mitre-collection'] || []; - + if (collections.length > 0) { const collection = collections[0]; expect(collection.x_mitre_attack_spec_version).toBeDefined(); @@ -38,13 +38,13 @@ describe('README.md Code Examples', () => { describe('Recommended Approach Example', () => { it('should work with the loading example from README', () => { - // Maps to: README.md - "Recommended Approach" section + // Maps to: README.md - "Recommended Approach" section // Code block: ```javascript const dataSource = new DataSourceRegistration({ source: 'attack', ... }); const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', version: '17.1', - parsingMode: 'strict' + parsingMode: 'strict', }); expect(dataSource.options.source).toBe('attack'); @@ -62,7 +62,7 @@ describe('README.md Code Examples', () => { source: 'attack', domain: 'enterprise-attack', version: '15.1', - parsingMode: 'relaxed' + parsingMode: 'relaxed', }); expect(dataSource.options.source).toBe('attack'); @@ -79,7 +79,7 @@ describe('README.md Code Examples', () => { expect(techniques.length).toBeGreaterThan(100); expect(tactics.length).toBeGreaterThan(5); - + if (techniques.length > 0) { const technique = techniques[0]; expect(technique.name).toBeDefined(); @@ -92,17 +92,17 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "Basic Usage" section // Code pattern: if (technique.x_mitre_is_subtechnique) { console.log(technique.getParentTechnique()); } const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; - - const parentTechnique = techniques.find(t => - 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === false + + const parentTechnique = techniques.find( + (t) => 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === false, ); if (parentTechnique) { expect(parentTechnique.x_mitre_is_subtechnique).toBe(false); } - const subtechnique = techniques.find(t => - 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === true + const subtechnique = techniques.find( + (t) => 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === true, ); if (subtechnique) { @@ -140,10 +140,10 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "Parsing and Validating a Tactic" section // Validates that real objects have the structure shown in examples const tactics = globalThis.attackData.objectsByType['x-mitre-tactic'] || []; - + if (tactics.length > 0) { const tactic = tactics[0]; - + expect(tactic.id).toBeDefined(); expect(tactic.type).toBe('x-mitre-tactic'); expect(tactic.name).toBeDefined(); @@ -158,8 +158,8 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "Handling Invalid Data" section // Code block: ```typescript const invalidTactic = { id: "...", type: "x-mitre-tactic" }; const invalidTactic = { - id: "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", - type: "x-mitre-tactic", + id: 'x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5', + type: 'x-mitre-tactic', // Missing required fields like name, description, etc. }; @@ -174,11 +174,11 @@ describe('README.md Code Examples', () => { // Maps to: README.md - "How It Works" section, step 5 // Text: "automatically processes all 'relationship' objects in the dataset" const relationships = globalThis.attackData.sros || []; - + expect(relationships.length).toBeGreaterThan(0); - - const usesRelationship = relationships.find(rel => rel.relationship_type === 'uses'); - + + const usesRelationship = relationships.find((rel) => rel.relationship_type === 'uses'); + if (usesRelationship) { expect(usesRelationship.source_ref).toBeDefined(); expect(usesRelationship.target_ref).toBeDefined(); @@ -191,7 +191,7 @@ describe('README.md Code Examples', () => { it('should demonstrate AttackDataModel instantiation', () => { // Maps to: README.md - Various sections showing AttackDataModel usage // Code pattern: new AttackDataModel(uuid, objects) constructor usage - const uuid = "test-unique-id"; + const uuid = 'test-unique-id'; const attackObjects: any[] = []; const testDataModel = new AttackDataModel(uuid, attackObjects); @@ -199,4 +199,4 @@ describe('README.md Code Examples', () => { expect(typeof testDataModel.getUuid()).toBe('string'); }); }); -}); \ No newline at end of file +}); diff --git a/test/documentation/USAGE.test.ts b/test/documentation/USAGE.test.ts index b14a6451..e6acdfe0 100644 --- a/test/documentation/USAGE.test.ts +++ b/test/documentation/USAGE.test.ts @@ -1,11 +1,7 @@ import { describe, it, expect } from 'vitest'; import { z } from 'zod'; import { createSyntheticStixObject } from '../../src/generator/index.js'; -import { - tacticSchema, - campaignSchema, - techniqueSchema -} from '../../src/schemas/index.js'; +import { tacticSchema, campaignSchema, techniqueSchema } from '../../src/schemas/index.js'; import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; import { AttackDataModel } from '../../src/classes/attack-data-model.js'; @@ -14,21 +10,21 @@ describe('docs/USAGE.md Code Examples', () => { it('should work with ESM example', () => { // Maps to: docs/USAGE.md - "ESM Usage Example" section // Code block: ```javascript import { AttackDataModel } from '@mitre-attack/attack-data-model'; - const uuid = "my-unique-id"; + const uuid = 'my-unique-id'; const attackObjects: any[] = []; const attackDataModel = new AttackDataModel(uuid, attackObjects); - expect(attackDataModel.getUuid()).toBe("my-unique-id"); + expect(attackDataModel.getUuid()).toBe('my-unique-id'); }); it('should work with CommonJS pattern', () => { // Maps to: docs/USAGE.md - "CommonJS Usage Example" section // Code block: ```javascript const { AttackDataModel } = require('@mitre-attack/attack-data-model'); - const uuid = "my-unique-id"; + const uuid = 'my-unique-id'; const attackObjects: any[] = []; const attackDataModel = new AttackDataModel(uuid, attackObjects); - expect(attackDataModel.getUuid()).toBe("my-unique-id"); + expect(attackDataModel.getUuid()).toBe('my-unique-id'); }); }); @@ -49,7 +45,7 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Accessing Schemas" section // Code block: ```typescript import { campaignSchema } from '@mitre-attack/attack-data-model'; const validCampaign = createSyntheticStixObject('campaign'); - + expect(() => { const parsedCampaign = campaignSchema.parse(validCampaign); expect(parsedCampaign.name).toBeDefined(); @@ -62,7 +58,7 @@ describe('docs/USAGE.md Code Examples', () => { // Note: We can't test the exact extend example due to refinement issues mentioned in docs // Instead, we test that the base schema works as expected const campaign = createSyntheticStixObject('campaign'); - + expect(() => { const parsed = campaignSchema.parse(campaign); expect(parsed).toBeDefined(); @@ -73,12 +69,12 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Schema refinements" section // Text: "validating that the first reference in external_references contains a valid ATT&CK ID" const technique = createSyntheticStixObject('attack-pattern'); - + expect(technique?.external_references).toBeDefined(); expect(technique?.external_references?.[0]).toBeDefined(); expect(technique?.external_references?.[0].source_name).toBe('mitre-attack'); expect(technique?.external_references?.[0].external_id).toBeDefined(); - + expect(() => { techniqueSchema.parse(technique); }).not.toThrow(); @@ -116,12 +112,12 @@ describe('docs/USAGE.md Code Examples', () => { // Validates the hierarchical structure: STIX base → ATT&CK base → specific objects const validTechnique = createSyntheticStixObject('attack-pattern'); const parsed = techniqueSchema.parse(validTechnique); - + // Should have STIX base properties expect(parsed.id).toBeDefined(); expect(parsed.type).toBe('attack-pattern'); expect(parsed.spec_version).toBeDefined(); - + // Should have ATT&CK-specific properties expect(parsed.x_mitre_domains).toBeDefined(); expect(parsed.x_mitre_attack_spec_version).toBeDefined(); @@ -132,7 +128,7 @@ describe('docs/USAGE.md Code Examples', () => { it('should demonstrate AttackDataModel basic usage', () => { // Maps to: docs/USAGE.md - "AttackDataModel Class" section // Code block: ```typescript const attackDataModel = new AttackDataModel(); - const uuid = "test-uuid"; + const uuid = 'test-uuid'; const attackObjects: any[] = []; const attackDataModel = new AttackDataModel(uuid, attackObjects); @@ -152,7 +148,7 @@ describe('docs/USAGE.md Code Examples', () => { source: 'attack', domain: 'enterprise-attack', version: '15.1', - parsingMode: 'relaxed' + parsingMode: 'relaxed', }); expect(dataSource.options.source).toBe('attack'); @@ -168,10 +164,10 @@ describe('docs/USAGE.md Code Examples', () => { // Code block: ```typescript const techniques = campaign.getTechniques(); // Test using real data to validate relationship patterns exist const campaigns = globalThis.attackData.objectsByType['campaign'] || []; - + if (campaigns.length > 0) { const campaign = campaigns[0]; - + // Properties mentioned in the relationship mapping examples expect(campaign.name).toBeDefined(); expect(campaign.id).toBeDefined(); @@ -185,7 +181,7 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Examples" section // Code block: ```typescript techniques.forEach((technique) => { const tactics = technique.getTactics(); }); const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; - + if (techniques.length > 0) { const technique = techniques[0]; expect(technique.name).toBeDefined(); @@ -197,7 +193,7 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Working with Software and Groups" section // Code block: ```typescript malwareList.forEach((malware) => { const associatedGroups = malware.getAssociatedGroups(); }); const malware = globalThis.attackData.objectsByType['malware'] || []; - + if (malware.length > 0) { const malwareItem = malware[0]; expect(malwareItem.name).toBeDefined(); @@ -209,7 +205,7 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Validating and Parsing a Technique" section // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; const rawTechniqueData = createSyntheticStixObject('attack-pattern'); - + expect(() => { const technique = techniqueSchema.parse(rawTechniqueData); expect(technique.name).toBeDefined(); @@ -220,8 +216,8 @@ describe('docs/USAGE.md Code Examples', () => { // Maps to: docs/USAGE.md - "Handling Invalid Data" section // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; const invalidTechniqueData = { - id: "attack-pattern--1234abcd-5678-efgh-ijkl-9876mnopqrst", - type: "attack-pattern", + id: 'attack-pattern--1234abcd-5678-efgh-ijkl-9876mnopqrst', + type: 'attack-pattern', // Missing required fields }; @@ -239,11 +235,11 @@ describe('docs/USAGE.md Code Examples', () => { source: 'attack', domain: 'enterprise-attack', version: '15.1', - parsingMode: 'relaxed' + parsingMode: 'relaxed', }); expect(dataSource.options.source).toBe('attack'); expect(dataSource.options.parsingMode).toBe('relaxed'); }); }); -}); \ No newline at end of file +}); diff --git a/test/documentation/first-query.test.ts b/test/documentation/first-query.test.ts index 1fc042b6..178b410f 100644 --- a/test/documentation/first-query.test.ts +++ b/test/documentation/first-query.test.ts @@ -25,7 +25,7 @@ describe('examples/first-query.ts Code Example', () => { it('should validate AttackDataModel methods used in the example', () => { // Maps to: examples/first-query.ts - lines 22-31 // Code: const attackDataModel = loadDataModel(uuid); attackDataModel.techniques.length; attackDataModel.techniques.slice(0, 5) - const uuid = "test-uuid"; + const uuid = 'test-uuid'; const attackObjects: any[] = []; const attackDataModel = new AttackDataModel(uuid, attackObjects); @@ -33,10 +33,10 @@ describe('examples/first-query.ts Code Example', () => { expect(attackDataModel.techniques).toBeDefined(); expect(Array.isArray(attackDataModel.techniques)).toBe(true); expect(typeof attackDataModel.techniques.length).toBe('number'); - + // The example calls .slice(0, 5) on techniques array expect(typeof attackDataModel.techniques.slice).toBe('function'); - + // Test that slice works as expected in the example const sliced = attackDataModel.techniques.slice(0, 5); expect(Array.isArray(sliced)).toBe(true); @@ -49,18 +49,18 @@ describe('examples/first-query.ts Code Example', () => { // Maps to: examples/first-query.ts - line 29 // Code: technique.name and technique.external_references[0].external_id const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; - + if (techniques.length > 0) { const technique = techniques[0]; - + // Properties accessed in the example expect(technique.name).toBeDefined(); expect(technique.external_references).toBeDefined(); expect(Array.isArray(technique.external_references)).toBe(true); - + if (technique.external_references.length > 0) { expect(technique.external_references[0].external_id).toBeDefined(); - + // The example formats output as: `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})` const formattedOutput = `1. ${technique.name} (${technique.external_references[0].external_id})`; expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); @@ -72,14 +72,14 @@ describe('examples/first-query.ts Code Example', () => { // Maps to: examples/first-query.ts - line 28-31 // Code: forEach((technique, index) => { console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); }); const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; - + // Test the first 5 techniques as the example does const first5Techniques = techniques.slice(0, 5); - + first5Techniques.forEach((technique, index) => { expect(technique.name).toBeDefined(); expect(technique.external_references?.[0]?.external_id).toBeDefined(); - + // Validate the exact output format from the example const formattedOutput = `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`; expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); @@ -93,7 +93,7 @@ describe('examples/first-query.ts Code Example', () => { it('should validate the async function structure used in the example', () => { // Maps to: examples/first-query.ts - lines 3-38 // Code: async function exploreAttackData() { try { const uuid = await registerDataSource(dataSource); } catch (error) { ... } } - + // Test that the async pattern components work const testAsyncPattern = async () => { const dataSource = new DataSourceRegistration({ @@ -108,7 +108,7 @@ describe('examples/first-query.ts Code Example', () => { expect(dataSource.options.domain).toBeDefined(); expect(dataSource.options.version).toBeDefined(); expect(dataSource.options.parsingMode).toBeDefined(); - + return dataSource; }; @@ -120,14 +120,14 @@ describe('examples/first-query.ts Code Example', () => { it('should validate console output message formats from the example', () => { // Maps to: examples/first-query.ts - lines 4, 19, 23, 26 // Code: console.log('🎯 Loading ATT&CK Enterprise data...\n'); console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); - + const techniqueCount = globalThis.attackData.objectsByType['attack-pattern']?.length || 0; - + // Test the loading message format from the example const loadingMessage = `Loaded ${techniqueCount} techniques`; expect(loadingMessage).toMatch(/^Loaded \d+ techniques$/); expect(techniqueCount).toBeGreaterThan(0); - + // Test the exploration message format const explorationMessage = 'First 5 techniques:'; expect(explorationMessage).toBe('First 5 techniques:'); @@ -138,13 +138,13 @@ describe('examples/first-query.ts Code Example', () => { it('should validate error handling structure from the example', () => { // Maps to: examples/first-query.ts - lines 32-37 // Code: } else { console.error('❌ Failed to register data source'); } } catch (error) { console.error('❌ Error:', error); } - + // Test that the error handling patterns would work const testErrorHandling = () => { try { // Simulate the uuid check pattern from the example - const uuid = "test-uuid"; // In real example, this comes from registerDataSource - + const uuid = 'test-uuid'; // In real example, this comes from registerDataSource + if (uuid) { expect(uuid).toBeDefined(); expect(typeof uuid).toBe('string'); @@ -166,11 +166,11 @@ describe('examples/first-query.ts Code Example', () => { it('should validate the imports used in the example', () => { // Maps to: examples/first-query.ts - line 1 // Code: import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; - + // Test that the imported classes/functions exist and are usable expect(DataSourceRegistration).toBeDefined(); expect(typeof DataSourceRegistration).toBe('function'); - + // Test that DataSourceRegistration can be instantiated as shown in the example const dataSource = new DataSourceRegistration({ source: 'attack', @@ -178,8 +178,8 @@ describe('examples/first-query.ts Code Example', () => { version: '17.1', parsingMode: 'relaxed', }); - + expect(dataSource).toBeInstanceOf(DataSourceRegistration); }); }); -}); \ No newline at end of file +}); diff --git a/test/global.d.ts b/test/global.d.ts index ba5223f5..83a34bcd 100644 --- a/test/global.d.ts +++ b/test/global.d.ts @@ -2,5 +2,5 @@ import { getAttackObjects } from './utils/attack-data'; declare global { - var attackData: Awaited>; -} \ No newline at end of file + var attackData: Awaited>; +} diff --git a/test/objects/analytic.test.ts b/test/objects/analytic.test.ts index 2b5c103c..e765a3ed 100644 --- a/test/objects/analytic.test.ts +++ b/test/objects/analytic.test.ts @@ -1,9 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type Analytic, analyticSchema, LogSourceRef } from '../../src/schemas/sdo/analytic.schema'; describe('analyticSchema', () => { diff --git a/test/objects/asset.test.ts b/test/objects/asset.test.ts index 53fbe8af..738254e1 100644 --- a/test/objects/asset.test.ts +++ b/test/objects/asset.test.ts @@ -1,8 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - xMitreIdentity -} from '../../src/schemas/common/index'; +import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Asset, assetSchema } from '../../src/schemas/sdo/asset.schema'; /** diff --git a/test/objects/campaign.test.ts b/test/objects/campaign.test.ts index 17e1ee96..e92391e6 100644 --- a/test/objects/campaign.test.ts +++ b/test/objects/campaign.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import type { - StixTimestamp -} from '../../src/schemas/common/index'; +import type { StixTimestamp } from '../../src/schemas/common/index'; import { type Campaign, campaignSchema } from '../../src/schemas/sdo/campaign.schema'; /** diff --git a/test/objects/data-component.test.ts b/test/objects/data-component.test.ts index 56f7f003..593614ab 100644 --- a/test/objects/data-component.test.ts +++ b/test/objects/data-component.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; import { - type DataComponent, - dataComponentSchema, + type DataComponent, + dataComponentSchema, } from '../../src/schemas/sdo/data-component.schema'; describe('dataComponentSchema', () => { diff --git a/test/objects/data-source.test.ts b/test/objects/data-source.test.ts index 3d40cc29..07d8177d 100644 --- a/test/objects/data-source.test.ts +++ b/test/objects/data-source.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type DataSource, dataSourceSchema } from '../../src/schemas/sdo/data-source.schema'; describe('dataSourceSchema', () => { diff --git a/test/objects/detection-strategy.test.ts b/test/objects/detection-strategy.test.ts index 9a3031ee..1e6394a2 100644 --- a/test/objects/detection-strategy.test.ts +++ b/test/objects/detection-strategy.test.ts @@ -1,10 +1,11 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { - type ExternalReferences -} from '../../src/schemas/common/index'; -import { type DetectionStrategy, detectionStrategySchema } from '../../src/schemas/sdo/detection-strategy.schema'; + type DetectionStrategy, + detectionStrategySchema, +} from '../../src/schemas/sdo/detection-strategy.schema'; describe('detectionStrategySchema', () => { const minimalDetectionStrategy = createSyntheticStixObject('x-mitre-detection-strategy'); @@ -44,7 +45,11 @@ describe('detectionStrategySchema', () => { }); describe('Field-Specific Tests', () => { - const testField = (fieldName: keyof DetectionStrategy, invalidValue: any, isRequired = true) => { + const testField = ( + fieldName: keyof DetectionStrategy, + invalidValue: any, + isRequired = true, + ) => { it(`should reject invalid values for ${fieldName}`, () => { const invalidObject = { ...minimalDetectionStrategy, [fieldName]: invalidValue }; expect(() => detectionStrategySchema.parse(invalidObject)).toThrow(); @@ -284,4 +289,4 @@ describe('detectionStrategySchema', () => { expect(() => detectionStrategySchema.parse(detectionStrategyWithVariousUUIDs)).not.toThrow(); }); }); -}); \ No newline at end of file +}); diff --git a/test/objects/group.test.ts b/test/objects/group.test.ts index 186cd71b..2cddc7d2 100644 --- a/test/objects/group.test.ts +++ b/test/objects/group.test.ts @@ -1,9 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import type { - Description -} from '../../src/schemas/common/index'; +import type { Description } from '../../src/schemas/common/index'; import { type Group, groupSchema } from '../../src/schemas/sdo/group.schema'; /** diff --git a/test/objects/log-source.test.ts b/test/objects/log-source.test.ts index acc819b9..7d327538 100644 --- a/test/objects/log-source.test.ts +++ b/test/objects/log-source.test.ts @@ -1,9 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type LogSource, logSourceSchema } from '../../src/schemas/sdo/log-source.schema'; describe('logSourceSchema', () => { diff --git a/test/objects/malware.test.ts b/test/objects/malware.test.ts index be9861a8..d5fbffeb 100644 --- a/test/objects/malware.test.ts +++ b/test/objects/malware.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type StixTimestamp -} from '../../src/schemas/common/index'; +import { type StixTimestamp } from '../../src/schemas/common/index'; import { type Malware, malwareSchema } from '../../src/schemas/sdo/malware.schema'; describe('MalwareSchema', () => { diff --git a/test/objects/mitigation.test.ts b/test/objects/mitigation.test.ts index 040bb832..690785b5 100644 --- a/test/objects/mitigation.test.ts +++ b/test/objects/mitigation.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type Mitigation, mitigationSchema } from '../../src/schemas/sdo/mitigation.schema'; describe('MitigationSchema', () => { diff --git a/test/objects/relationship.test.ts b/test/objects/relationship.test.ts index ed895fb3..731f379a 100644 --- a/test/objects/relationship.test.ts +++ b/test/objects/relationship.test.ts @@ -3,21 +3,21 @@ import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import { z } from 'zod'; import { createSyntheticStixObject } from '../../src/generator'; import { - type Description, - type ExternalReferences, - type StixCreatedTimestamp, - type StixIdentifier, - type StixModifiedTimestamp, - type StixSpecVersion, - type StixType + type Description, + type ExternalReferences, + type StixCreatedTimestamp, + type StixIdentifier, + type StixModifiedTimestamp, + type StixSpecVersion, + type StixType, } from '../../src/schemas/common/index'; import { - invalidRelationships, - isValidRelationship, - type Relationship, - relationshipSchema, - type RelationshipType, - validRelationshipObjectTypes + invalidRelationships, + isValidRelationship, + type Relationship, + relationshipSchema, + type RelationshipType, + validRelationshipObjectTypes, } from '../../src/schemas/sro/relationship.schema'; import { logger } from '../utils/logger'; @@ -283,7 +283,7 @@ describe('RelationshipSchema', () => { const validRelationships: Relationship[] = []; const errors: { relationship: Relationship; issues: z.ZodIssue[] }[] = []; - for (let relationship of relationships) { + for (const relationship of relationships) { const result = relationshipSchema.safeParse(relationship); if (result.success) { validRelationships.push(relationship); diff --git a/test/objects/technique.test.ts b/test/objects/technique.test.ts index e70c51a1..fe5a0610 100644 --- a/test/objects/technique.test.ts +++ b/test/objects/technique.test.ts @@ -1,8 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - xMitreIdentity -} from '../../src/schemas/common/index'; +import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Technique, techniqueSchema } from '../../src/schemas/sdo/technique.schema'; /** From c636861165be67ef10ef4f227438d55fc82c2da7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:38:37 -0400 Subject: [PATCH 23/39] feat(technique.schema): add meta description to techniqueSchema --- src/schemas/sdo/technique.schema.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/schemas/sdo/technique.schema.ts b/src/schemas/sdo/technique.schema.ts index 4309637a..4d00e635 100644 --- a/src/schemas/sdo/technique.schema.ts +++ b/src/schemas/sdo/technique.schema.ts @@ -368,11 +368,14 @@ export const techniqueSchema = attackBaseDomainObjectSchema x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional(), }) + .meta({ + description: + 'Techniques describe specific methods adversaries use to achieve tactical objectives and are represented as [attack-pattern](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230921) objects following the STIX 2.1 specification.', + }) .strict() .check((ctx) => { createAttackIdInExternalReferencesRefinement()(ctx); createEnterpriseOnlyPropertiesRefinement()(ctx); createMobileOnlyPropertiesRefinement()(ctx); }); - export type Technique = z.infer; From b327ae10a3ad8685ce5bca105833b4e1e59fa807 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:40:11 -0400 Subject: [PATCH 24/39] refactor(stix-bundle.schema): simplify objects validator with discriminatedUnion --- src/schemas/sdo/stix-bundle.schema.ts | 160 +++++--------------------- 1 file changed, 27 insertions(+), 133 deletions(-) diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index fa7d702d..be6b8b2e 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -2,11 +2,8 @@ import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { createStixIdValidator } from '../common/stix-identifier.js'; import { createStixTypeValidator } from '../common/stix-type.js'; -import { - type MarkingDefinition, - markingDefinitionSchema, -} from '../smo/marking-definition.schema.js'; -import { type Relationship, relationshipSchema } from '../sro/relationship.schema.js'; +import { type MarkingDefinition } from '../smo/marking-definition.schema.js'; +import { type Relationship } from '../sro/relationship.schema.js'; import { type Analytic, analyticSchema } from './analytic.schema.js'; import { type Asset, assetSchema } from './asset.schema.js'; import { type Campaign, campaignSchema } from './campaign.schema.js'; @@ -43,132 +40,6 @@ export type AttackObject = | Relationship | MarkingDefinition; -// Create a schema mapping object to map types to their schemas -const schemaMap = { - get malware() { - return malwareSchema; - }, - get 'x-mitre-asset'() { - return assetSchema; - }, - get campaign() { - return campaignSchema; - }, - get 'x-mitre-collection'() { - return collectionSchema; - }, - get 'x-mitre-data-component'() { - return dataComponentSchema; - }, - get 'x-mitre-data-source'() { - return dataSourceSchema; - }, - get 'x-mitre-detection-strategy'() { - return detectionStrategySchema; - }, - get 'x-mitre-log-source'() { - return logSourceSchema; - }, - get 'x-mitre-analytic'() { - return analyticSchema; - }, - get identity() { - return identitySchema; - }, - get 'x-mitre-matrix'() { - return matrixSchema; - }, - get tool() { - return toolSchema; - }, - get 'x-mitre-tactic'() { - return tacticSchema; - }, - get 'attack-pattern'() { - return techniqueSchema; - }, - get 'intrusion-set'() { - return groupSchema; - }, - get 'course-of-action'() { - return mitigationSchema; - }, - get relationship() { - return relationshipSchema; - }, - get 'marking-definition'() { - return markingDefinitionSchema; - }, -}; - -// IMPORTANT: Casting the 'attackObjectsSchema' to 'z.ZodTypeAny' is critical. Without it, the TypeScript compiler will get overwhelmed and throw the following: -// 'error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.' - -export const attackObjectsSchema: z.ZodTypeAny = z - .array( - z - .object({ - // Basic structure validation to ensure we have a type field - type: z.string({ - error: (issue) => { - return issue.code === 'invalid_type' - ? "Object 'type' must be a string" - : "The 'type' property is invalid or missing"; - }, - }), - id: z.string({ - error: (issue) => { - return issue.code === 'invalid_type' - ? "Object 'id' must be a string" - : "The 'id' property is invalid or missing"; - }, - }), - }) - .loose() - .check((ctx) => { - const type = ctx.value.type; - - // Uncomment for debugging - // console.log(`Validating object of type: ${type}, ID: ${obj.id}`); - - // Get the schema based on the type - const schema = schemaMap[type as keyof typeof schemaMap]; - - if (!schema) { - ctx.issues.push({ - code: 'custom', - message: `Unknown STIX type: ${type}`, - path: ['type'], - input: ctx.value.type, - }); - return; - } - - // Validate the object against the appropriate schema - // TODO can the following code be cleaned up? - try { - schema.parse(ctx.value); - } catch (error) { - if (error instanceof z.ZodError) { - // Forward all validation issues from the schema - error.issues.forEach((issue) => { - ctx.issues.push(issue); - }); - } else { - // Handle unexpected errors - ctx.issues.push({ - code: 'custom', - message: `Validation error: ${error instanceof Error ? error.message : String(error)}`, - input: ctx.value, // TODO this might be too much information: how can we filter down to just the relevant part? - }); - } - } - }), - ) - .min(1, { message: 'The STIX bundle must contain at least one object.' }); - -export type AttackObjects = z.infer; - ///////////////////////////////////// // // STIX Bundle @@ -179,10 +50,33 @@ export const stixBundleSchema = z .object({ id: createStixIdValidator('bundle'), type: createStixTypeValidator('bundle'), - objects: attackObjectsSchema, + // objects: attackObjectsSchema, + objects: z + .array( + z.discriminatedUnion('type', [ + collectionSchema, + analyticSchema, + assetSchema, + campaignSchema, + dataComponentSchema, + dataSourceSchema, + detectionStrategySchema, + groupSchema, + identitySchema, + logSourceSchema, + malwareSchema, + matrixSchema, + mitigationSchema, + tacticSchema, + techniqueSchema, + toolSchema, + ]), + ) + .min(1), }) .meta({ - description: 'A Bundle is a collection of arbitrary STIX Objects grouped together in a single container. A Bundle does not have any semantic meaning and the objects contained within the Bundle are not considered related by virtue of being in the same Bundle. A STIX Bundle Object is not a STIX Object but makes use of the type and id Common Properties.' + description: + 'A Bundle is a collection of arbitrary STIX Objects grouped together in a single container. A Bundle does not have any semantic meaning and the objects contained within the Bundle are not considered related by virtue of being in the same Bundle. A STIX Bundle Object is not a STIX Object but makes use of the type and id Common Properties.', }) .strict() .check((ctx) => { From 834c80fba9dc12325c0fd774f81d1bd9bdc8d65b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:41:00 -0400 Subject: [PATCH 25/39] fix: remove nonexistant imports --- src/schemas/sdo/index.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/schemas/sdo/index.ts b/src/schemas/sdo/index.ts index 3ba8d8ff..d67236b8 100644 --- a/src/schemas/sdo/index.ts +++ b/src/schemas/sdo/index.ts @@ -8,7 +8,7 @@ export { type Asset, type RelatedAsset, type RelatedAssets, - type XMitreSectors, + type XMitreSectors } from './asset.schema.js'; export { @@ -17,21 +17,21 @@ export { xMitreLastSeenCitationSchema, type Campaign, type XMitreFirstSeenCitation, - type XMitreLastSeenCitation, + type XMitreLastSeenCitation } from './campaign.schema.js'; export { collectionSchema, objectVersionReferenceSchema, type Collection, - type ObjectVersionReference, + type ObjectVersionReference } from './collection.schema.js'; export { dataComponentSchema, xMitreDataSourceRefSchema, type DataComponent, - type XMitreDataSourceRef, + type XMitreDataSourceRef } from './data-component.schema.js'; export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js'; @@ -44,14 +44,14 @@ export { logSourceSchema, xMitreLogSourcePermutationsSchema, type LogSource, - type XMitreLogSourcePermutations, + type XMitreLogSourcePermutations } from './log-source.schema.js'; export { dataSourceSchema, xMitreCollectionLayersSchema, type DataSource, - type XMitreCollectionLayers, + type XMitreCollectionLayers } from './data-source.schema.js'; export { malwareSchema, type Malware } from './malware.schema.js'; @@ -60,7 +60,7 @@ export { matrixSchema, xMitreTacticRefsSchema, type Matrix, - type XMitreTacticRefs, + type XMitreTacticRefs } from './matrix.schema.js'; export { mitigationSchema, type Mitigation } from './mitigation.schema.js'; @@ -71,7 +71,7 @@ export { tacticSchema, xMitreShortNameSchema, type Tactic, - type XMitreShortName, + type XMitreShortName } from './tactic.schema.js'; export { @@ -100,15 +100,14 @@ export { type XMitrePermissionsRequired, type XMitreRemoteSupport, type XMitreSystemRequirements, - type XMitreTacticType, + type XMitreTacticType } from './technique.schema.js'; export { toolSchema, type Tool } from './tool.schema.js'; export { - attackObjectsSchema, stixBundleSchema, type AttackObject, - type AttackObjects, - type StixBundle, + type StixBundle } from './stix-bundle.schema.js'; + From ee8a26f4da7cac6d62f7758f3629e750b4abfbb0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:42:45 -0400 Subject: [PATCH 26/39] docs: move stix-bundle.schema.md to reference folder --- docusaurus/docs/reference/schemas/.gitkeep | 0 docusaurus/docs/{sdo => reference/schemas}/stix-bundle.schema.md | 1 - 2 files changed, 1 deletion(-) delete mode 100644 docusaurus/docs/reference/schemas/.gitkeep rename docusaurus/docs/{sdo => reference/schemas}/stix-bundle.schema.md (74%) diff --git a/docusaurus/docs/reference/schemas/.gitkeep b/docusaurus/docs/reference/schemas/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/docusaurus/docs/sdo/stix-bundle.schema.md b/docusaurus/docs/reference/schemas/stix-bundle.schema.md similarity index 74% rename from docusaurus/docs/sdo/stix-bundle.schema.md rename to docusaurus/docs/reference/schemas/stix-bundle.schema.md index c1c6fea7..7453ca5c 100644 --- a/docusaurus/docs/sdo/stix-bundle.schema.md +++ b/docusaurus/docs/reference/schemas/stix-bundle.schema.md @@ -8,7 +8,6 @@ _Object containing the following properties:_ | :------- |:----------- | :--- | | **`id`** (\*) | | `any` | | **`type`** (\*)| | `'bundle'` | -| **`spec_version`** (\*) | The version of the STIX specification used to represent this object. The value of this property MUST be 2.1 for STIX Objects defined according to this specification. If objects are found where this property is not present, the implicit value for all STIX Objects other than SCOs is 2.0. Since SCOs are now top-level objects in STIX 2.1, the default value for SCOs is 2.1. | `'2.0' \| '2.1'` | | **`objects`** (\*) | | [AttackObjects](#attackobjects) | _(\*) Required._ From 574e7b3e52315024aa7cf39c6221ccf8f79286f4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:43:09 -0400 Subject: [PATCH 27/39] style: apply formatting --- src/schemas/sdo/index.ts | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/schemas/sdo/index.ts b/src/schemas/sdo/index.ts index d67236b8..a097ea7c 100644 --- a/src/schemas/sdo/index.ts +++ b/src/schemas/sdo/index.ts @@ -8,7 +8,7 @@ export { type Asset, type RelatedAsset, type RelatedAssets, - type XMitreSectors + type XMitreSectors, } from './asset.schema.js'; export { @@ -17,21 +17,21 @@ export { xMitreLastSeenCitationSchema, type Campaign, type XMitreFirstSeenCitation, - type XMitreLastSeenCitation + type XMitreLastSeenCitation, } from './campaign.schema.js'; export { collectionSchema, objectVersionReferenceSchema, type Collection, - type ObjectVersionReference + type ObjectVersionReference, } from './collection.schema.js'; export { dataComponentSchema, xMitreDataSourceRefSchema, type DataComponent, - type XMitreDataSourceRef + type XMitreDataSourceRef, } from './data-component.schema.js'; export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js'; @@ -44,14 +44,14 @@ export { logSourceSchema, xMitreLogSourcePermutationsSchema, type LogSource, - type XMitreLogSourcePermutations + type XMitreLogSourcePermutations, } from './log-source.schema.js'; export { dataSourceSchema, xMitreCollectionLayersSchema, type DataSource, - type XMitreCollectionLayers + type XMitreCollectionLayers, } from './data-source.schema.js'; export { malwareSchema, type Malware } from './malware.schema.js'; @@ -60,7 +60,7 @@ export { matrixSchema, xMitreTacticRefsSchema, type Matrix, - type XMitreTacticRefs + type XMitreTacticRefs, } from './matrix.schema.js'; export { mitigationSchema, type Mitigation } from './mitigation.schema.js'; @@ -71,7 +71,7 @@ export { tacticSchema, xMitreShortNameSchema, type Tactic, - type XMitreShortName + type XMitreShortName, } from './tactic.schema.js'; export { @@ -100,14 +100,9 @@ export { type XMitrePermissionsRequired, type XMitreRemoteSupport, type XMitreSystemRequirements, - type XMitreTacticType + type XMitreTacticType, } from './technique.schema.js'; export { toolSchema, type Tool } from './tool.schema.js'; -export { - stixBundleSchema, - type AttackObject, - type StixBundle -} from './stix-bundle.schema.js'; - +export { stixBundleSchema, type AttackObject, type StixBundle } from './stix-bundle.schema.js'; From dbc3ca18737d6720110cd91f218b53f9d256d2c5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:05:55 -0400 Subject: [PATCH 28/39] fix: remove nonexistant imports --- src/main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 4ba0b007..c3e86843 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,6 @@ import { v4 as uuidv4 } from 'uuid'; import { stixBundleSchema, type AttackObject, - type AttackObjects, type StixBundle, } from './schemas/sdo/stix-bundle.schema.js'; @@ -221,7 +220,7 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO } // Now process each object individually - const objects = rawData.objects as AttackObjects[]; + const objects = rawData.objects as AttackObject[]; for (let index = 0; index < objects.length; index++) { const obj = objects[index] as AttackObject; let objParseResult; From 64eae41c9c193af564fc5002ec658fddbe6093ef Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 19 Aug 2025 12:26:37 -0500 Subject: [PATCH 29/39] docs: update docs to state they are a work in progress --- README.md | 2 +- docusaurus/.gitignore | 3 - docusaurus/blog/known-issues.md | 10 - .../contributing/coding-style.md | 0 .../contributing/dev-setup.md | 10 +- docusaurus/docs/contributing/docs.md | 41 ++ docusaurus/docs/contributing/index.md | 12 + .../{how-to-guides => }/contributing/tests.md | 4 +- .../docs/explanation/identifier-systems.md | 484 ------------------ docusaurus/docs/explanation/index.md | 192 ------- .../docs/how-to-guides/contributing/docs.md | 27 - .../docs/how-to-guides/contributing/index.md | 15 - .../docs/how-to-guides/error-handling.md | 4 + docusaurus/docs/how-to-guides/index.md | 45 +- .../docs/how-to-guides/manage-data-sources.md | 4 + docusaurus/docs/how-to-guides/performance.md | 4 + .../docs/how-to-guides/validate-bundles.md | 4 + docusaurus/docs/index.md | 4 + docusaurus/docs/overview.md | 23 +- .../attack-specification-overview.md | 4 + .../compatibility.md | 4 + docusaurus/docs/principles/index.md | 184 +++++++ .../object-design-rationale.md | 4 + .../schema-design.md | 4 + .../stix-foundation.md | 4 + .../{explanation => principles}/trade-offs.md | 4 + .../versioning-philosophy.md | 4 + .../why-adm-exists.md | 24 +- .../docs/reference/api/attack-data-model.md | 4 + docusaurus/docs/reference/api/data-sources.md | 4 + docusaurus/docs/reference/api/index.md | 4 + docusaurus/docs/reference/api/utilities.md | 4 + docusaurus/docs/reference/configuration.md | 4 + docusaurus/docs/reference/errors.md | 4 + docusaurus/docs/reference/index.md | 4 + .../reference/schemas/stix-bundle.schema.md | 19 +- docusaurus/docs/tutorials/index.md | 39 +- .../docs/tutorials/multi-domain-analysis.md | 4 + docusaurus/docs/tutorials/relationships.md | 4 + .../docs/tutorials/technique-browser.md | 4 + docusaurus/docs/tutorials/your-first-query.md | 4 +- docusaurus/docusaurus.config.ts | 15 +- docusaurus/sidebars.ts | 92 +++- .../src/components/DocTypeIndicator/index.tsx | 6 +- .../src/components/HomepageFeatures/index.tsx | 65 +-- .../HomepageFeatures/styles.module.css | 14 +- .../src/components/WorkInProgressNotice.tsx | 10 + package.json | 4 +- 48 files changed, 495 insertions(+), 937 deletions(-) delete mode 100644 docusaurus/blog/known-issues.md rename docusaurus/docs/{how-to-guides => }/contributing/coding-style.md (100%) rename docusaurus/docs/{how-to-guides => }/contributing/dev-setup.md (66%) create mode 100644 docusaurus/docs/contributing/docs.md create mode 100644 docusaurus/docs/contributing/index.md rename docusaurus/docs/{how-to-guides => }/contributing/tests.md (84%) delete mode 100644 docusaurus/docs/explanation/identifier-systems.md delete mode 100644 docusaurus/docs/explanation/index.md delete mode 100644 docusaurus/docs/how-to-guides/contributing/docs.md delete mode 100644 docusaurus/docs/how-to-guides/contributing/index.md rename docusaurus/docs/{explanation => principles}/attack-specification-overview.md (96%) rename docusaurus/docs/{explanation => principles}/compatibility.md (96%) create mode 100644 docusaurus/docs/principles/index.md rename docusaurus/docs/{explanation => principles}/object-design-rationale.md (96%) rename docusaurus/docs/{explanation => principles}/schema-design.md (96%) rename docusaurus/docs/{explanation => principles}/stix-foundation.md (96%) rename docusaurus/docs/{explanation => principles}/trade-offs.md (96%) rename docusaurus/docs/{explanation => principles}/versioning-philosophy.md (96%) rename docusaurus/docs/{explanation => principles}/why-adm-exists.md (92%) create mode 100644 docusaurus/src/components/WorkInProgressNotice.tsx diff --git a/README.md b/README.md index 73e40f6f..59052047 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ For more detailed examples, please refer to the [examples](./examples/README.md) ## Compatibility Matrix -Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/explanation/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK dataset (`mitre-attack/attack-stix-data`). +Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/principles/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK dataset (`mitre-attack/attack-stix-data`). ## Contributing diff --git a/docusaurus/.gitignore b/docusaurus/.gitignore index e5394fb1..b2d6de30 100644 --- a/docusaurus/.gitignore +++ b/docusaurus/.gitignore @@ -18,6 +18,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - -docs/*/* -!docs/sdo/stix-bundle.schema.md diff --git a/docusaurus/blog/known-issues.md b/docusaurus/blog/known-issues.md deleted file mode 100644 index de205027..00000000 --- a/docusaurus/blog/known-issues.md +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/docusaurus/docs/how-to-guides/contributing/coding-style.md b/docusaurus/docs/contributing/coding-style.md similarity index 100% rename from docusaurus/docs/how-to-guides/contributing/coding-style.md rename to docusaurus/docs/contributing/coding-style.md diff --git a/docusaurus/docs/how-to-guides/contributing/dev-setup.md b/docusaurus/docs/contributing/dev-setup.md similarity index 66% rename from docusaurus/docs/how-to-guides/contributing/dev-setup.md rename to docusaurus/docs/contributing/dev-setup.md index 308a39b1..0f13ab1c 100644 --- a/docusaurus/docs/how-to-guides/contributing/dev-setup.md +++ b/docusaurus/docs/contributing/dev-setup.md @@ -10,20 +10,22 @@ cd attack-data-model npm install ``` -💡 The repo is a multi-package workspace (core library + docusaurus site). Running npm install at the root will also bootstrap the docs workspace. +💡 The repo is a multi-package workspace (core library + docusaurus site). +Running `npm install` at the root will NOT install the dependencies for building the docs. +Check out the [documentation](docs) contribution guide for more details. ## Verify TypeScript build ```bash -npm run build # compiles src → dist using tsup -npm run test # vitest smoke-test +npm run build # compiles src → dist using tsup +npm run test # vitest tests ``` ## Handy npm scripts | Script | Purpose | |---|---| -| npm run lint | Lints all src/** files | +| npm run lint | Lints all `src/**` files | | npm run format | Prettier + ESLint fix | | npm run test | Run tests | diff --git a/docusaurus/docs/contributing/docs.md b/docusaurus/docs/contributing/docs.md new file mode 100644 index 00000000..7357813c --- /dev/null +++ b/docusaurus/docs/contributing/docs.md @@ -0,0 +1,41 @@ +# Documentation + +This covers how we generate the documentation for the ATT&CK Data Model. + +All of these commands are run from the `docusaurus/` directory, so be sure to run this first. +Otherwise you may be very confused as to why these commands don't exist in the top-level `package.json`! + +```bash +cd docusaurus/ + +# don't forget to install the dependencies that are specific to building the documentation +npm install +``` + +## Auto-generating schema reference files + +The script `generate-docs.sh` introspects **all** `*.schema.ts` files and +emits Markdown to `docusaurus/docs/reference/schemas/*` which is git ignored. + +```bash +npm run gendocs +``` + +## Run the documentation site locally + +Running one of the following commands will enable you to open the site locally at [http://localhost:3000](http://localhost:3000). + +```bash +# opens your default browser automatically +npm start + +# doesn't open your browser +npm run -- --no-open +``` + +## Adding new how-to guides + +Its pretty easy to add a new guide. Basically just do the following: + +1. Create your Markdown file inside `docusaurus/docs//my-guide.md` +2. Update `sidebars.ts` to include it diff --git a/docusaurus/docs/contributing/index.md b/docusaurus/docs/contributing/index.md new file mode 100644 index 00000000..6fa35dba --- /dev/null +++ b/docusaurus/docs/contributing/index.md @@ -0,0 +1,12 @@ +import DocCardList from '@theme/DocCardList'; + +# Contributing + +**Step-by-step technical instructions for contributors** + +This section explains how to contribute to the ATT&CK Data Model repository. +It complements the high-level `CONTRIBUTING.md` in the root of the repository by focusing on specific developer tasks. + +## Topics + + diff --git a/docusaurus/docs/how-to-guides/contributing/tests.md b/docusaurus/docs/contributing/tests.md similarity index 84% rename from docusaurus/docs/how-to-guides/contributing/tests.md rename to docusaurus/docs/contributing/tests.md index d17b0826..74e2fe66 100644 --- a/docusaurus/docs/how-to-guides/contributing/tests.md +++ b/docusaurus/docs/contributing/tests.md @@ -1,9 +1,11 @@ # Running and Writing Tests -The project uses **[Vitest](https://vitest.dev/)**. +The ATT&CK Data Model uses **[Vitest](https://vitest.dev/)** for testing. ## Execute the full suite +Running the tests is straightforward enough: + ```bash npm test ``` diff --git a/docusaurus/docs/explanation/identifier-systems.md b/docusaurus/docs/explanation/identifier-systems.md deleted file mode 100644 index 20e0ab6e..00000000 --- a/docusaurus/docs/explanation/identifier-systems.md +++ /dev/null @@ -1,484 +0,0 @@ -# Identifier Systems - -**Understanding ATT&CK's multiple identification schemes and their purposes** - -ATT&CK employs three distinct identifier systems, each serving different purposes and use cases. This multi-layered approach often confuses newcomers who expect a single, simple identifier scheme. Understanding why multiple identifiers exist, how they interact, and when to use each type is crucial for effective ATT&CK data processing and integration. - -## The Identification Challenge - -### Competing Requirements - -ATT&CK must serve multiple audiences with conflicting identification needs: - -#### Human Communication - -Security analysts need memorable, meaningful identifiers for documentation, reports, and discussions: - -- "We observed T1055 (Process Injection) being used by APT1" -- "Our detection rules cover techniques T1055.001 through T1055.012" - -#### Machine Processing - -Applications need globally unique, collision-resistant identifiers for reliable data processing: - -- Database primary keys and foreign key relationships -- Cross-system references and data synchronization -- Programmatic lookups and relationship traversal - -#### Standards Compliance - -STIX 2.1 requires specific identifier formats and uniqueness guarantees: - -- UUID-based identifiers for global uniqueness -- Namespace prefixes for object type identification -- Referential integrity across STIX bundles - -### The Impossibility of One Perfect Identifier - -No single identifier scheme can satisfy all these requirements simultaneously: - -- **Human-readable IDs** (like "T1055") are meaningful but not globally unique -- **Globally unique IDs** (like UUIDs) are collision-resistant but not memorable -- **Sequential integers** are simple but create coordination problems across systems -- **Hash-based IDs** are deterministic but not human-interpretable - -**Solution**: Use multiple complementary identifier systems, each optimized for specific use cases. - -## The Three Identifier Systems - -### 1. STIX IDs: The Primary Identity System - -**Format**: `{object-type}--{uuid}` -**Example**: `attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298` - -#### Purpose and Characteristics - -**Primary identifiers**: Every ATT&CK object has exactly one STIX ID that serves as its canonical identity. - -**Global uniqueness**: UUIDs ensure no identifier collisions across all ATT&CK datasets, organizations, and time periods. - -**Standards compliance**: Required by STIX 2.1 specification for referential integrity and cross-system compatibility. - -**Referential integrity**: All object references in relationships use STIX IDs: - -```json -{ - "type": "relationship", - "relationship_type": "subtechnique-of", - "source_ref": "attack-pattern--sub-technique-stix-id", - "target_ref": "attack-pattern--parent-technique-stix-id" -} -``` - -#### When to Use STIX IDs - -**✅ Recommended for**: - -- Programmatic object lookups and storage -- Database primary keys and foreign keys -- API endpoints and data synchronization -- Internal application logic and caching - -**❌ Not recommended for**: - -- User interfaces and human communication -- Documentation and report writing -- Manual analysis and investigation workflows - -#### Working with STIX IDs - -```typescript -// ✅ Correct usage - programmatic lookup -const technique = attackDataModel.getTechniqueById( - "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" -); - -// ❌ Avoid - hard to read and maintain -const displayName = "Technique attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298"; -``` - -### 2. ATT&CK IDs: The Human-Readable System - -**Format**: Varies by object type -**Examples**: `T1055`, `G0006`, `M1038`, `TA0002` - -#### Format Patterns by Object Type - -| Object Type | Format | Example | Description | -|-------------|--------|---------|-------------| -| Technique | `Txxxx` | `T1055` | Four-digit numeric sequence | -| Sub-technique | `Txxxx.yyy` | `T1055.001` | Parent technique + sub-technique suffix | -| Tactic | `TAxxxx` | `TA0002` | "TA" prefix + four-digit number | -| Group | `Gxxxx` | `G0006` | "G" prefix + four-digit number | -| Software | `Sxxxx` | `S0154` | "S" prefix + four-digit number | -| Mitigation | `Mxxxx` | `M1038` | "M" prefix + four-digit number | -| Data Source | `DSxxxx` | `DS0017` | "DS" prefix + four-digit number | -| Data Component | `DCxxxx` | `DC0024` | "DC" prefix + four-digit number | - -#### Storage as External References - -ATT&CK IDs are stored in the first external reference, not as primary properties: - -```json -{ - "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "T1055", - "url": "https://attack.mitre.org/techniques/T1055" - } - ] -} -``` - -**Design rationale**: This approach maintains STIX compliance while preserving human-readable identifiers as metadata. - -#### Uniqueness Limitations - -**Important**: ATT&CK IDs are not guaranteed to be globally unique. - -**Historical collisions**: Legacy mitigations (pre-v5, July 2019) may share ATT&CK IDs with techniques due to deprecated 1:1 relationships. - -**Matrix sharing**: Matrices within the same domain use identical ATT&CK IDs (e.g., both Enterprise matrices use "enterprise-attack"). - -**Filtering strategy**: - -```typescript -// Filter out deprecated objects to avoid ID collisions -const currentTechniques = techniques.filter(t => - !t.revoked && !t.x_mitre_deprecated -); -``` - -#### When to Use ATT&CK IDs - -**✅ Recommended for**: - -- User interfaces and documentation -- Reports and human communication -- Manual analysis workflows -- External tool integration where human readability matters - -**❌ Not recommended for**: - -- Database primary keys (use STIX IDs) -- Programmatic object references (use STIX IDs) -- Critical system integration where uniqueness is required - -### 3. External Reference IDs: The Integration System - -**Format**: Varies by external framework -**Examples**: `NIST-Mobile-ID`, `CAPEC-123` - -#### Purpose and Usage - -External reference IDs link ATT&CK objects to concepts in other security frameworks and standards. - -**Common external frameworks**: - -- **NIST Mobile Threat Catalogue**: Found on Mobile domain techniques -- **CAPEC (Common Attack Pattern Enumeration and Classification)**: Found on Enterprise techniques -- **CVE references**: Found on software objects for specific vulnerabilities - -#### Storage Pattern - -Multiple external references can exist beyond the primary ATT&CK ID: - -```json -{ - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "T1055", - "url": "https://attack.mitre.org/techniques/T1055" - }, - { - "source_name": "capec", - "external_id": "CAPEC-640", - "url": "https://capec.mitre.org/data/definitions/640.html" - } - ] -} -``` - -#### When to Use External Reference IDs - -**✅ Recommended for**: - -- Cross-framework mapping and correlation -- Integration with other security standards -- Research that spans multiple threat intelligence frameworks -- Compliance reporting that requires framework cross-references - -## Identifier Resolution Patterns - -### Lookup by ATT&CK ID - -The most common query pattern involves finding objects by human-readable ATT&CK ID: - -```typescript -// Manual approach - searching external references -const technique = attackDataModel.techniques.find(t => - t.external_references?.[0]?.external_id === "T1055" && - t.external_references?.[0]?.source_name === "mitre-attack" -); - -// Library approach - optimized lookup -const technique = attackDataModel.getTechniqueByAttackId("T1055"); -``` - -**Performance consideration**: ATT&CK ID lookups require searching external references arrays, which is less efficient than STIX ID lookups. Libraries typically build indexes to optimize this pattern. - -### Bidirectional Conversion - -Applications often need to convert between identifier types: - -```typescript -// ATT&CK ID → STIX ID -const stixId = attackDataModel.getStixIdFromAttackId("T1055"); - -// STIX ID → ATT&CK ID -const attackId = attackDataModel.getAttackIdFromStixId( - "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" -); - -// Object → Both IDs -const technique = attackDataModel.getTechniqueByAttackId("T1055"); -console.log(`STIX ID: ${technique.id}`); -console.log(`ATT&CK ID: ${technique.getAttackId()}`); -``` - -### Relationship Reference Resolution - -Relationships use STIX IDs, but applications may need to display ATT&CK IDs: - -```json -{ - "type": "relationship", - "relationship_type": "uses", - "source_ref": "intrusion-set--stix-id-for-apt1", - "target_ref": "attack-pattern--stix-id-for-t1055" -} -``` - -```typescript -// Display relationship with human-readable IDs -const relationship = /* ... */; -const group = attackDataModel.getGroupById(relationship.source_ref); -const technique = attackDataModel.getTechniqueById(relationship.target_ref); - -console.log(`${group.getAttackId()} uses ${technique.getAttackId()}`); -// Output: "G0006 uses T1055" -``` - -## Identifier Evolution and Management - -### ID Assignment Process - -#### STIX IDs - -- **Generated**: Automatically created using UUID generation algorithms -- **Immutable**: Never change once assigned, even when object content is updated -- **Globally coordinated**: UUID algorithms ensure uniqueness without central coordination - -#### ATT&CK IDs - -- **Curated**: Manually assigned by MITRE ATT&CK team following semantic patterns -- **Mostly stable**: Generally preserved across object updates, but may change in exceptional circumstances -- **Centrally coordinated**: MITRE maintains the canonical assignment registry - -### Version Management Impact - -Identifier behavior during object evolution: - -#### Content Updates - -```json -{ - "id": "attack-pattern--unchanged-stix-id", - "external_references": [ - { - "external_id": "T1055", // ATT&CK ID typically unchanged - "source_name": "mitre-attack" - } - ], - "x_mitre_version": "2.0", // Version increments - "modified": "2023-06-15T10:30:00.000Z" // Timestamp updates -} -``` - -#### Object Replacement (Revocation) - -When objects are substantially restructured, they may be revoked and replaced: - -```json -// Old object -{ - "id": "attack-pattern--old-stix-id", - "revoked": true -} - -// Replacement relationship -{ - "type": "relationship", - "relationship_type": "revoked-by", - "source_ref": "attack-pattern--old-stix-id", - "target_ref": "attack-pattern--new-stix-id" -} -``` - -**Impact**: Both STIX and ATT&CK IDs may change during revocation, requiring applications to follow replacement chains. - -## Common Identifier Mistakes - -### 1. Using ATT&CK IDs as Primary Keys - -**❌ Problematic**: - -```typescript -// ATT&CK IDs are not guaranteed unique -const techniqueCache = new Map(); -technique.forEach(t => { - techniqueCache.set(t.getAttackId(), t); // May overwrite due to collisions -}); -``` - -**✅ Correct**: - -```typescript -// Use STIX IDs for reliable uniqueness -const techniqueCache = new Map(); -techniques.forEach(t => { - techniqueCache.set(t.id, t); // STIX IDs are guaranteed unique -}); -``` - -### 2. Hard-coding STIX IDs - -**❌ Problematic**: - -```typescript -// STIX IDs are not meaningful to humans -if (technique.id === "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298") { - // This is unreadable and unmaintainable -} -``` - -**✅ Correct**: - -```typescript -// Use ATT&CK IDs for human-readable logic -if (technique.getAttackId() === "T1055") { - // Clear intent and maintainable -} -``` - -### 3. Assuming ATT&CK ID Stability - -**❌ Problematic**: - -```typescript -// Assuming ATT&CK IDs never change -const savedAttackId = "T1055"; -// Later... (may fail if ID was reassigned) -const technique = getTechniqueByAttackId(savedAttackId); -``` - -**✅ Better**: - -```typescript -// Store STIX IDs for long-term reliability -const savedStixId = technique.id; -// Later... (guaranteed to work unless object is revoked) -const technique = getTechniqueById(savedStixId); -``` - -### 4. Ignoring External Reference Structure - -**❌ Problematic**: - -```typescript -// Assuming ATT&CK ID is always first external reference -const attackId = technique.external_references[0].external_id; -``` - -**✅ Correct**: - -```typescript -// Properly validate external reference structure -const attackRef = technique.external_references?.find(ref => - ref.source_name === "mitre-attack" && ref.external_id -); -const attackId = attackRef?.external_id; -``` - -## Best Practices for Identifier Management - -### 1. Use the Right Identifier for the Task - -**Storage and processing**: Use STIX IDs -**User interfaces and communication**: Use ATT&CK IDs -**Cross-framework integration**: Use external reference IDs - -### 2. Build Lookup Indexes - -```typescript -class AttackDataModel { - private stixToAttackId = new Map(); - private attackIdToStix = new Map(); - - constructor(techniques: Technique[]) { - techniques.forEach(technique => { - const attackId = this.extractAttackId(technique); - if (attackId) { - this.stixToAttackId.set(technique.id, attackId); - this.attackIdToStix.set(attackId, technique.id); - } - }); - } - - getTechniqueByAttackId(attackId: string): Technique | undefined { - const stixId = this.attackIdToStix.get(attackId); - return stixId ? this.getTechniqueById(stixId) : undefined; - } -} -``` - -### 3. Handle Identifier Edge Cases - -```typescript -function safeGetAttackId(object: AttackObject): string | undefined { - // Handle objects without ATT&CK IDs (like relationships) - const attackRef = object.external_references?.find(ref => - ref.source_name === "mitre-attack" && ref.external_id - ); - - return attackRef?.external_id; -} -``` - -### 4. Plan for Identifier Evolution - -```typescript -// Design APIs that can adapt to identifier changes -interface TechniqueReference { - stixId: string; // Primary, stable identifier - attackId?: string; // Secondary, may change - lastValidated: Date; // Track when reference was verified -} -``` - ---- - -## The Multi-Identifier Philosophy - -ATT&CK's multiple identifier systems reflect the reality that different use cases have fundamentally different requirements. Rather than forcing a compromise that serves no use case well, the framework provides specialized identifiers optimized for specific scenarios: - -- **STIX IDs** for reliable machine processing -- **ATT&CK IDs** for effective human communication -- **External reference IDs** for framework integration - -Understanding when and how to use each identifier system is essential for building robust, maintainable ATT&CK applications that serve both human and machine users effectively. - -**Next**: Explore the rationale behind **[Versioning Philosophy](./versioning-philosophy)** and how ATT&CK manages evolution across multiple dimensions. diff --git a/docusaurus/docs/explanation/index.md b/docusaurus/docs/explanation/index.md deleted file mode 100644 index ae586dea..00000000 --- a/docusaurus/docs/explanation/index.md +++ /dev/null @@ -1,192 +0,0 @@ -# Explanation - -**Understanding-oriented content about design decisions and architecture** - -This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. - -## Design Philosophy - -### Why These Explanations Matter - -Understanding the reasoning behind design decisions helps you: - -- **Make informed choices** about how to use the library in your projects -- **Extend the library** in ways that align with its architecture -- **Contribute effectively** to the project's development -- **Troubleshoot issues** by understanding underlying mechanisms -- **Architect systems** that work well with the library's strengths - -## Available Explanations - -### Foundational Context - -- **[Why the ATT&CK Data Model Exists](./why-adm-exists)** - The problem context, solution approach, and value proposition -- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX was chosen and how it shapes the architecture - -### ATT&CK Specification Understanding - -- **[ATT&CK Specification Overview](./attack-specification-overview)** - Understanding the structure and purpose of the ATT&CK specification -- **[Object Design Rationale](./object-design-rationale)** - Why ATT&CK objects are structured the way they are -- **[Identifier Systems](./identifier-systems)** - Understanding ATT&CK's multiple identification schemes and their purposes -- **[Versioning Philosophy](./versioning-philosophy)** - Understanding ATT&CK's multi-dimensional versioning approach - -### Technical Architecture - -- **[Schema Design Principles](./schema-design)** - Validation philosophy, refinement patterns, and extensibility choices - -## Core Principles - -The ATT&CK Data Model is built on several key principles that influence all design decisions: - -### 1. STIX 2.1 Compliance First - -**Principle**: Maintain strict compatibility with STIX 2.1 specification -**Implication**: All ATT&CK objects are valid STIX objects first, ATT&CK objects second -**Trade-off**: Sometimes requires more complex APIs to maintain standard compliance - -**Example**: Using STIX relationship objects instead of embedded references, even though embedded references might be more convenient for some use cases. - -### 2. Type Safety Without Performance Cost - -**Principle**: Provide strong TypeScript types while maintaining runtime performance -**Implication**: Heavy use of Zod schemas for both validation and type inference -**Trade-off**: Larger bundle size in exchange for compile-time safety and runtime validation - -**Example**: Every ATT&CK object has both a Zod schema (for validation) and TypeScript interface (for typing), even though this creates some duplication. - -### 3. Relationship-First Navigation - -**Principle**: ATT&CK's value comes from object relationships, so navigation must be intuitive -**Implication**: Implementation classes provide relationship methods that abstract STIX relationship complexity -**Trade-off**: Memory overhead for relationship indexing in exchange for fast navigation - -**Example**: `technique.getTactics()` abstracts the underlying STIX relationships and provides immediate access to related objects. - -### 4. Extensibility Through Standards - -**Principle**: Support customization without breaking compatibility -**Implication**: Extensions follow STIX custom property conventions -**Trade-off**: More verbose extension syntax but guaranteed interoperability - -**Example**: Custom fields use `x_custom_` prefixes and require manual refinement reapplication, following STIX best practices. - -## Architectural Themes - -### Layered Architecture - -The library uses a three-layer architecture: - -1. **Schema Layer** - Zod schemas for validation and type inference -2. **Class Layer** - Implementation classes with relationship navigation -3. **Data Access Layer** - Data sources and loading mechanisms - -Each layer serves distinct purposes and can be used independently or together. - -### Immutable Data Model - -**Decision**: ATT&CK objects are immutable after loading -**Rationale**: Prevents accidental modification of shared threat intelligence data -**Implication**: Any modifications require creating new objects or data models - -### Lazy Relationship Resolution - -**Decision**: Relationships are resolved on-demand rather than eagerly -**Rationale**: Better memory usage and faster initial loading -**Implication**: First access to relationships may be slightly slower than subsequent accesses - -## Design Evolution - -### Historical Context - -The ATT&CK Data Model evolved through several design phases: - -1. **Simple JSON Processing** - Direct JSON manipulation without validation -2. **Schema-First Design** - Introduction of Zod validation schemas -3. **Relationship Navigation** - Addition of implementation classes with navigation methods -4. **Multi-Source Support** - Extensible data source architecture -5. **Type Safety** - Full TypeScript integration with proper type inference - -Each evolution maintained backward compatibility while addressing real-world usage patterns. - -### Current Design State - -The current architecture reflects lessons learned from: - -- **Enterprise deployments** requiring strict validation -- **Research applications** needing flexible data exploration -- **Integration scenarios** demanding standards compliance -- **Performance requirements** in high-throughput systems - -## Understanding Through Examples - -### Why Zod Instead of JSON Schema? - -**Context**: Need for runtime validation and TypeScript integration -**Decision**: Use Zod for schema definition -**Alternatives Considered**: JSON Schema, Joi, Yup - -**Rationale**: - -- Zod provides TypeScript type inference automatically -- Runtime validation matches compile-time types exactly -- Schema definitions are more maintainable in TypeScript -- Better error messages for developers - -**Trade-offs**: - -- Zod is less universally known than JSON Schema -- Schemas are tied to TypeScript ecosystem -- Larger runtime bundle due to Zod dependency - -### Why Implementation Classes Instead of Plain Objects? - -**Context**: Need for relationship navigation without breaking STIX compliance -**Decision**: Create implementation classes that extend plain objects with methods -**Alternatives Considered**: Plain objects with utility functions, custom object shapes - -**Rationale**: - -- Object-oriented navigation is more intuitive (`technique.getTactics()`) -- Encapsulation keeps relationship logic organized -- TypeScript provides better IntelliSense for methods -- Classes can be extended by library users - -**Trade-offs**: - -- Increased memory usage due to method inheritance -- More complex object construction process -- Learning curve for users expecting plain JSON objects - -## When to Read These Explanations - -### Before Building Applications - -Read **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand whether this library fits your use case and how it compares to alternatives. - -### When Working with ATT&CK Data - -Read **[ATT&CK Specification Overview](./attack-specification-overview)** to understand the structure and purpose of the ATT&CK specification, then **[Identifier Systems](./identifier-systems)** to understand how to work with ATT&CK's multiple identification schemes effectively. - -### When Building Data Processing Logic - -Read **[Object Design Rationale](./object-design-rationale)** to understand why ATT&CK objects are structured the way they are, and **[Versioning Philosophy](./versioning-philosophy)** to handle evolution and compatibility correctly. - -### When Extending Functionality - -Read **[Schema Design Principles](./schema-design)** to understand how to extend schemas and create custom validation rules that align with the library's patterns. - -### When Contributing Code - -Read **[STIX 2.1 as the Foundation](./stix-foundation)** to understand the standards compliance requirements that guide all development decisions. - -## Discussion and Feedback - -These explanations reflect the current understanding and rationale behind design decisions. If you have questions about specific choices or suggestions for different approaches: - -- **Open a GitHub Discussion** for architectural questions -- **Create an Issue** for specific design concerns -- **Join the community** to discuss alternative approaches - ---- - -**Ready to dive deeper?** Start with **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand the foundational context that shapes everything else. diff --git a/docusaurus/docs/how-to-guides/contributing/docs.md b/docusaurus/docs/how-to-guides/contributing/docs.md deleted file mode 100644 index 3c8720cc..00000000 --- a/docusaurus/docs/how-to-guides/contributing/docs.md +++ /dev/null @@ -1,27 +0,0 @@ -# Updating Documentation - -## Auto-generating schema reference files - -The script `generate-docs.sh` introspects **all** `*.schema.ts` files and -emits Markdown to `docusaurus/docs/reference/schemas/*` which is git ignored. - -```bash -cd docusaurus -npm run gendocs -``` - -## Run the documentation site locally (optional) - -```bash -npm start # http://localhost:3000 -``` - -## Adding new how-to guides - -1. Create your Markdown file inside `docusaurus/docs//my-guide.md` -2. Update `sidebars.ts` or rely on *autogenerated* sidebars by placing the file in an existing directory. - -## Writing reference pages - -For API reference (classes, functions), prefer TypeDoc or embed short code blocks. -Long-form tutorials belong under `docs/tutorials/`. diff --git a/docusaurus/docs/how-to-guides/contributing/index.md b/docusaurus/docs/how-to-guides/contributing/index.md deleted file mode 100644 index c1492e0a..00000000 --- a/docusaurus/docs/how-to-guides/contributing/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# Contributing - -**Step-by-step technical instructions for contributors** - -This section explains how to contribute to the ATT&CK Data Model repository. -It complements the high-level `CONTRIBUTING.md` in the root of the repository by focusing on specific developer tasks. - -## Sub-topics - -| Guide | What you’ll learn | -|-------|-------------------| -| [Development Setup](./dev-setup) | Cloning, installing dependencies, and common npm scripts | -| [Coding Style & Linting](./coding-style) | ESLint, Prettier, and commit-lint rules | -| [Running and Writing Tests](./tests) | Vitest workflow, coverage, and test helpers | -| [Updating Docs & Schemas](./docs) | Auto-generating docs from Zod schemas and writing how-to guides | diff --git a/docusaurus/docs/how-to-guides/error-handling.md b/docusaurus/docs/how-to-guides/error-handling.md index 00018cb7..2e08d45c 100644 --- a/docusaurus/docs/how-to-guides/error-handling.md +++ b/docusaurus/docs/how-to-guides/error-handling.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # How to Handle Parsing Errors Gracefully + + **Implement robust error handling for production applications** When working with ATT&CK data in production, you need comprehensive error handling to manage data source failures, validation errors, and parsing issues. This guide shows you how to build resilient applications that handle errors gracefully while providing meaningful feedback. diff --git a/docusaurus/docs/how-to-guides/index.md b/docusaurus/docs/how-to-guides/index.md index 8cd5d41e..c25249a2 100644 --- a/docusaurus/docs/how-to-guides/index.md +++ b/docusaurus/docs/how-to-guides/index.md @@ -1,50 +1,17 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; +import DocCardList from '@theme/DocCardList'; + # How-to Guides + + **Problem-oriented solutions for specific ATT&CK Data Model tasks** These guides provide direct solutions to common problems you'll encounter when working with the ATT&CK Data Model. Each guide assumes you have basic familiarity with the library and focuses on achieving specific goals quickly and efficiently. ## Available Guides -### Data Validation & Quality - -- **[How to validate custom STIX bundles](./validate-bundles)** - Ensure your custom ATT&CK data meets specification requirements -- **[How to handle parsing errors gracefully](./error-handling)** - Implement robust error handling for production applications - -### Contributing - -- **[Development setup](./contributing/dev-setup)** - Clone, install, and run the project locally -- **[Coding style & linting](./contributing/coding-style)** - ESLint, Prettier, commit rules -- **[Running and writing tests](./contributing/tests)** - Vitest workflow and coverage -- **[Updating docs & schemas](./contributing/docs)** - Auto-generating docs and writing guides - -## Quick Solutions - -**Looking for something specific?** Use these shortcuts: - -| Problem | Solution | -|---------|----------| -| My custom STIX data fails validation | [Validate Custom Bundles](./validate-bundles) | -| I need to add custom fields to techniques | [Extend Schemas](./extend-schemas) | -| My application crashes on invalid data | [Error Handling](./error-handling) | -| I want to use multiple data sources | [Reference: DataSource API](../reference/api/data-sources) | -| I need to optimize performance | [Explanation: Performance Trade-offs](../explanation/trade-offs) | - -## How-to Guide Principles - -These guides follow a problem-solving approach: - -- **Goal-oriented**: Each guide solves a specific problem -- **Assumes knowledge**: You should be familiar with basic ATT&CK Data Model concepts -- **Practical focus**: Direct steps to achieve your goal -- **Flexible solutions**: Adaptable to different scenarios - -## Getting More Help - -- **New to the library?** Start with our [Tutorials](../tutorials/) -- **Need complete API details?** Check the [Reference](../reference/) -- **Want to understand design decisions?** Read the [Explanations](../explanation/) -- **Have a specific question?** Search the documentation or check existing [GitHub Issues](https://github.com/mitre-attack/attack-data-model/issues) + --- diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.md b/docusaurus/docs/how-to-guides/manage-data-sources.md index ec739629..c7a1088a 100644 --- a/docusaurus/docs/how-to-guides/manage-data-sources.md +++ b/docusaurus/docs/how-to-guides/manage-data-sources.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # How to Manage Data Sources + + **Switch between different ATT&CK data sources efficiently** This guide shows you how to manage multiple ATT&CK data sources, switch between different versions, and work with local files, URLs, and the official repository. diff --git a/docusaurus/docs/how-to-guides/performance.md b/docusaurus/docs/how-to-guides/performance.md index d44205bc..319863ef 100644 --- a/docusaurus/docs/how-to-guides/performance.md +++ b/docusaurus/docs/how-to-guides/performance.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # How to Optimize Performance + + **Scale ATT&CK Data Model for large datasets and production workloads** This guide shows you how to optimize performance when working with large ATT&CK datasets, multiple domains, or production applications with high throughput requirements. diff --git a/docusaurus/docs/how-to-guides/validate-bundles.md b/docusaurus/docs/how-to-guides/validate-bundles.md index c7346e70..c72b0bb5 100644 --- a/docusaurus/docs/how-to-guides/validate-bundles.md +++ b/docusaurus/docs/how-to-guides/validate-bundles.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # How to Validate Custom STIX Bundles + + **Ensure your custom ATT&CK data meets specification requirements** When working with custom STIX 2.1 bundles containing ATT&CK data, you need to validate that they conform to both the STIX specification and ATT&CK schema requirements. This guide shows you how to implement comprehensive validation for your custom data. diff --git a/docusaurus/docs/index.md b/docusaurus/docs/index.md index 8d58bd5f..e1199a01 100644 --- a/docusaurus/docs/index.md +++ b/docusaurus/docs/index.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # ATT&CK Data Model Documentation + + *A TypeScript library for working with MITRE ATT&CK data using STIX 2.1 bundles* Welcome to the documentation for the ATT&CK Data Model library. diff --git a/docusaurus/docs/overview.md b/docusaurus/docs/overview.md index 2b615180..fc7f2871 100644 --- a/docusaurus/docs/overview.md +++ b/docusaurus/docs/overview.md @@ -1,4 +1,8 @@ -# ATT&CK Data Model Overview +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Overview + + **A comprehensive TypeScript library for MITRE ATT&CK data** @@ -10,11 +14,11 @@ The ATT&CK Data Model (ADM) is a TypeScript library that provides type-safe, pro ### Core Value Proposition -- **🔒 Type Safety**: Full TypeScript support prevents runtime errors -- **✅ STIX 2.1 Compliance**: Maintains standards compliance while adding usability -- **🔗 Relationship Navigation**: Intuitive methods for exploring ATT&CK connections -- **📊 Multi-Domain Support**: Works with Enterprise, Mobile, and ICS domains -- **🚀 Performance Optimized**: Designed for both memory efficiency and query speed +- **Type Safety**: Full TypeScript support prevents runtime errors +- **STIX 2.1 Compliance**: Maintains standards compliance while adding usability +- **Relationship Navigation**: Intuitive methods for exploring ATT&CK connections +- **Multi-Domain Support**: Works with Enterprise, Mobile, and ICS domains +- **Performance Optimized**: Designed for both memory efficiency and query speed ## Architecture Overview @@ -228,10 +232,3 @@ const refinedSchema = customTechniqueSchema.check((data) => { - Detection rule development --- - -**Ready to dive deeper?** Explore our comprehensive documentation: - -- **[Tutorials](./tutorials/)** - Learn by building -- **[How-to Guides](./how-to-guides/)** - Solve specific problems -- **[Reference](./reference/)** - Complete API documentation -- **[Explanation](./explanation/)** - Understand the design diff --git a/docusaurus/docs/explanation/attack-specification-overview.md b/docusaurus/docs/principles/attack-specification-overview.md similarity index 96% rename from docusaurus/docs/explanation/attack-specification-overview.md rename to docusaurus/docs/principles/attack-specification-overview.md index 54c0d0cf..63b5a35b 100644 --- a/docusaurus/docs/explanation/attack-specification-overview.md +++ b/docusaurus/docs/principles/attack-specification-overview.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # ATT&CK Specification Overview + + **Understanding the structure and purpose of the ATT&CK specification** The ATT&CK specification defines the formal structure, semantics, and constraints that govern how MITRE ATT&CK data is represented, validated, and consumed. Understanding the specification's design philosophy and architecture is crucial for working effectively with ATT&CK data and building robust applications that leverage the ATT&CK framework. diff --git a/docusaurus/docs/explanation/compatibility.md b/docusaurus/docs/principles/compatibility.md similarity index 96% rename from docusaurus/docs/explanation/compatibility.md rename to docusaurus/docs/principles/compatibility.md index 1d1b6a1f..5d5161b9 100644 --- a/docusaurus/docs/explanation/compatibility.md +++ b/docusaurus/docs/principles/compatibility.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Compatibility + + **Understanding version relationships and compatibility across the ATT&CK ecosystem** The ATT&CK Data Model operates within a complex ecosystem of interconnected version dependencies. Understanding these relationships helps you make informed decisions about version selection, upgrade timing, and compatibility management for your applications. diff --git a/docusaurus/docs/principles/index.md b/docusaurus/docs/principles/index.md new file mode 100644 index 00000000..5712cd54 --- /dev/null +++ b/docusaurus/docs/principles/index.md @@ -0,0 +1,184 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; +import { DocTypeCard } from '@site/src/components/DocTypeIndicator'; + +# Guiding Principles + + + +**Understanding-oriented content about design decisions and architecture** + +This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. + +## Dive Deeper + +### Foundational Context + +
+
+ +
+
+ +
+
+ +### ATT&CK Specification Understanding + +- **[ATT&CK Specification Overview](./attack-specification-overview)** - Understanding the structure and purpose of the ATT&CK specification +- **[Object Design Rationale](./object-design-rationale)** - Why ATT&CK objects are structured the way they are +- **[Versioning Philosophy](./versioning-philosophy)** - Understanding ATT&CK's multi-dimensional versioning approach + +### Technical Architecture + +- **[Schema Design Principles](./schema-design)** - Validation philosophy, refinement patterns, and extensibility choices + +### Miscellaneous + +- **[Compatibility](./compatibility)** - Compatibility matrix of versions of ATT&CK, the data model, and the library +- **[Architecture & Design Trade-offs](./trade-offs)** - Reasons why architecture decisions were made + +## Core Principles + +The ATT&CK Data Model is built on several key principles that influence all design decisions: + +### STIX 2.1 Compliance First + +- **Principle**: Maintain strict compatibility with STIX 2.1 specification +- **Implication**: All ATT&CK objects are valid STIX objects first, ATT&CK objects second +- **Trade-off**: Sometimes requires more complex APIs to maintain standard compliance + +**Example**: Using STIX relationship objects instead of embedded references, even though embedded references might be more convenient for some use cases. + +### Type Safety Without Performance Cost + +- **Principle**: Provide strong TypeScript types while maintaining runtime performance +- **Implication**: Heavy use of Zod schemas for both validation and type inference +- **Trade-off**: Larger bundle size in exchange for compile-time safety and runtime validation + +**Example**: Every ATT&CK object has both a Zod schema (for validation) and TypeScript interface (for typing), even though this creates some duplication. + +### Relationship-First Navigation + +- **Principle**: ATT&CK's value comes from object relationships, so navigation must be intuitive +- **Implication**: Implementation classes provide relationship methods that abstract STIX relationship complexity +- **Trade-off**: Memory overhead for relationship indexing in exchange for fast navigation + +**Example**: `technique.getTactics()` abstracts the underlying STIX relationships and provides immediate access to related objects. + +### Extensibility Through Standards + +- **Principle**: Support customization without breaking compatibility +- **Implication**: Extensions follow STIX custom property conventions +- **Trade-off**: More verbose extension syntax but guaranteed interoperability + +**Example**: Custom fields use `x_custom_` prefixes and require manual refinement reapplication, following STIX best practices. + +## Architectural Themes + +### Layered Architecture + +The library uses a three-layer architecture: + +1. **Schema Layer** - Zod schemas for validation and type inference +2. **Class Layer** - Implementation classes with relationship navigation +3. **Data Access Layer** - Data sources and loading mechanisms + +Each layer serves distinct purposes and can be used independently or together. + +### Immutable Data Model + +- **Decision**: ATT&CK objects are immutable after loading +- **Rationale**: Prevents accidental modification of shared threat intelligence data +- **Implication**: Any modifications require creating new objects or data models + +### Lazy Relationship Resolution + +- **Decision**: Relationships are resolved on-demand rather than eagerly +- **Rationale**: Better memory usage and faster initial loading +- **Implication**: First access to relationships may be slightly slower than subsequent accesses + +## Design Evolution + +### Historical Context + +The ATT&CK Data Model evolved through several design phases: + +1. **Simple JSON Processing** - Direct JSON manipulation without validation +2. **Schema-First Design** - Introduction of Zod validation schemas +3. **Relationship Navigation** - Addition of implementation classes with navigation methods +4. **Multi-Source Support** - Extensible data source architecture +5. **Type Safety** - Full TypeScript integration with proper type inference + +Each evolution maintained backward compatibility while addressing real-world usage patterns. + +### Current Design State + +The current architecture reflects lessons learned from: + +- **Enterprise deployments** requiring strict validation +- **Research applications** needing flexible data exploration +- **Integration scenarios** demanding standards compliance +- **Performance requirements** in high-throughput systems + +## Understanding Through Examples + +### Why Zod Instead of JSON Schema? + +- **Context**: Need for runtime validation and TypeScript integration +- **Decision**: Use Zod for schema definition +- **Alternatives Considered**: JSON Schema, Joi, Yup + +**Rationale**: + +- Zod provides TypeScript type inference automatically +- Runtime validation matches compile-time types exactly +- Schema definitions are more maintainable in TypeScript +- Better error messages for developers + +**Trade-offs**: + +- Zod is less universally known than JSON Schema +- Schemas are tied to TypeScript ecosystem +- Larger runtime bundle due to Zod dependency + +### Why Implementation Classes Instead of Plain Objects? + +- **Context**: Need for relationship navigation without breaking STIX compliance +- **Decision**: Create implementation classes that extend plain objects with methods +- **Alternatives Considered**: Plain objects with utility functions, custom object shapes + +**Rationale**: + +- Object-oriented navigation is more intuitive (`technique.getTactics()`) +- Encapsulation keeps relationship logic organized +- TypeScript provides better IntelliSense for methods +- Classes can be extended by library users + +**Trade-offs**: + +- Increased memory usage due to method inheritance +- More complex object construction process +- Learning curve for users expecting plain JSON objects + +## Discussion and Feedback + +These explanations reflect the current understanding and rationale behind design decisions. +If you have questions about specific choices or suggestions for different approaches: + +- **Open a GitHub Discussion** for architectural questions +- **Create an Issue** for specific design concerns +- **Join the community** to discuss alternative approaches + +--- + +**Ready to dive deeper?** Start with **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand the foundational context that shapes everything else. diff --git a/docusaurus/docs/explanation/object-design-rationale.md b/docusaurus/docs/principles/object-design-rationale.md similarity index 96% rename from docusaurus/docs/explanation/object-design-rationale.md rename to docusaurus/docs/principles/object-design-rationale.md index 9887e9f1..c2033812 100644 --- a/docusaurus/docs/explanation/object-design-rationale.md +++ b/docusaurus/docs/principles/object-design-rationale.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Object Design Rationale + + **Why ATT&CK objects are structured the way they are** The design of ATT&CK objects reflects careful consideration of competing requirements: STIX standards compliance, semantic accuracy, performance implications, and usability trade-offs. Each design decision represents a specific choice between alternatives, with clear rationales and acknowledged trade-offs. Understanding these decisions helps you work more effectively with ATT&CK data and make informed choices when extending the framework. diff --git a/docusaurus/docs/explanation/schema-design.md b/docusaurus/docs/principles/schema-design.md similarity index 96% rename from docusaurus/docs/explanation/schema-design.md rename to docusaurus/docs/principles/schema-design.md index 45725a66..4bcea19c 100644 --- a/docusaurus/docs/explanation/schema-design.md +++ b/docusaurus/docs/principles/schema-design.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Schema Design Principles + + **Validation philosophy, refinement patterns, and extensibility choices** The schema layer is the foundation that enables both runtime validation and compile-time type safety in the ATT&CK Data Model. The design of these schemas reflects careful consideration of competing concerns: STIX compliance, ATT&CK domain rules, TypeScript integration, extensibility, and performance. This explanation explores the principles that guide schema design decisions. diff --git a/docusaurus/docs/explanation/stix-foundation.md b/docusaurus/docs/principles/stix-foundation.md similarity index 96% rename from docusaurus/docs/explanation/stix-foundation.md rename to docusaurus/docs/principles/stix-foundation.md index 4a16ecb1..5c66b21d 100644 --- a/docusaurus/docs/explanation/stix-foundation.md +++ b/docusaurus/docs/principles/stix-foundation.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # STIX 2.1 as the Foundation + + **Why STIX was chosen and how it shapes the architecture** The decision to build the ATT&CK Data Model on STIX 2.1 as a foundation was neither obvious nor simple. This explanation explores why STIX was chosen, what alternatives were considered, and how this foundational choice shapes every aspect of the library's architecture. diff --git a/docusaurus/docs/explanation/trade-offs.md b/docusaurus/docs/principles/trade-offs.md similarity index 96% rename from docusaurus/docs/explanation/trade-offs.md rename to docusaurus/docs/principles/trade-offs.md index 1e40a99e..e4b3a956 100644 --- a/docusaurus/docs/explanation/trade-offs.md +++ b/docusaurus/docs/principles/trade-offs.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Architecture and Design Trade-offs + + **Understanding the key decisions and compromises in the ATT&CK Data Model** Software architecture involves countless trade-offs where optimizing for one goal requires sacrificing another. diff --git a/docusaurus/docs/explanation/versioning-philosophy.md b/docusaurus/docs/principles/versioning-philosophy.md similarity index 96% rename from docusaurus/docs/explanation/versioning-philosophy.md rename to docusaurus/docs/principles/versioning-philosophy.md index df320652..97ee1943 100644 --- a/docusaurus/docs/explanation/versioning-philosophy.md +++ b/docusaurus/docs/principles/versioning-philosophy.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Versioning Philosophy + + **Understanding ATT&CK's multi-dimensional versioning approach** ATT&CK employs a sophisticated three-dimensional versioning system that tracks changes across different scopes and timeframes. This approach often confuses users who expect simple sequential versioning, but the complexity serves important purposes: enabling independent evolution of standards, specifications, and content while maintaining compatibility across the ecosystem. Understanding this versioning philosophy is crucial for building robust applications and managing long-term compatibility. diff --git a/docusaurus/docs/explanation/why-adm-exists.md b/docusaurus/docs/principles/why-adm-exists.md similarity index 92% rename from docusaurus/docs/explanation/why-adm-exists.md rename to docusaurus/docs/principles/why-adm-exists.md index 5cb0a7d8..c649d0f1 100644 --- a/docusaurus/docs/explanation/why-adm-exists.md +++ b/docusaurus/docs/principles/why-adm-exists.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Why the ATT&CK Data Model Exists + + **The problem context, solution approach, and value proposition** The ATT&CK Data Model library didn't emerge in a vacuum - it was created to solve specific, recurring problems that security teams and researchers face when working with MITRE ATT&CK data. Understanding these problems and the solution approach helps clarify when and why to use this library. @@ -26,7 +30,7 @@ MITRE ATT&CK, while invaluable for threat intelligence and security operations, ### Before the ATT&CK Data Model -Prior to this library, developers typically handled ATT&CK data in one of these ways: +Prior to this library, developers typically handled ATT&CK data in one of two ways: #### 1. Direct JSON Manipulation @@ -79,24 +83,6 @@ class AttackDataParser { - No standard patterns across teams - Maintenance burden for each team -#### 3. Generic STIX Libraries - -```javascript -// Generic STIX approach - doesn't understand ATT&CK semantics -const stixParser = new STIXParser(); -const bundle = stixParser.parse(attackData); - -// Generic STIX relationships don't understand ATT&CK-specific patterns -const relationships = bundle.relationships.filter(/* ... */); -``` - -**Problems**: - -- No ATT&CK-specific validation -- Missing ATT&CK domain knowledge -- No awareness of ATT&CK conventions -- Generic tools don't optimize for ATT&CK use cases - ### The Core Problems Through observing these patterns across security teams, several core problems emerged: diff --git a/docusaurus/docs/reference/api/attack-data-model.md b/docusaurus/docs/reference/api/attack-data-model.md index f6c09c97..4efb19a6 100644 --- a/docusaurus/docs/reference/api/attack-data-model.md +++ b/docusaurus/docs/reference/api/attack-data-model.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # AttackDataModel + + **Main class containing all ATT&CK objects with automatic relationship mapping** The `AttackDataModel` class is the central data structure that holds all ATT&CK objects and provides access to them through typed properties. It automatically processes relationships between objects and provides convenient access methods. diff --git a/docusaurus/docs/reference/api/data-sources.md b/docusaurus/docs/reference/api/data-sources.md index b0a6eb35..06c90204 100644 --- a/docusaurus/docs/reference/api/data-sources.md +++ b/docusaurus/docs/reference/api/data-sources.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # DataSource + + **Data source configuration and registration for loading ATT&CK datasets** The `DataSource` class defines where and how to load ATT&CK data. It supports multiple source types including the official ATT&CK repository, local files, remote URLs, and TAXII servers. diff --git a/docusaurus/docs/reference/api/index.md b/docusaurus/docs/reference/api/index.md index 81779bac..a0197770 100644 --- a/docusaurus/docs/reference/api/index.md +++ b/docusaurus/docs/reference/api/index.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # API Reference + + **Complete documentation for all ATT&CK Data Model classes and functions** The ATT&CK Data Model provides a comprehensive TypeScript API for working with MITRE ATT&CK data. This reference section documents all public classes, methods, and functions available in the library. diff --git a/docusaurus/docs/reference/api/utilities.md b/docusaurus/docs/reference/api/utilities.md index 5bf59b84..4a5c983f 100644 --- a/docusaurus/docs/reference/api/utilities.md +++ b/docusaurus/docs/reference/api/utilities.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Utility Functions + + **Helper functions and data manipulation tools** The ATT&CK Data Model provides various utility functions for common data manipulation, validation, and analysis tasks. These functions complement the main classes and provide convenient ways to work with ATT&CK data. diff --git a/docusaurus/docs/reference/configuration.md b/docusaurus/docs/reference/configuration.md index dd0aa94b..2e39c354 100644 --- a/docusaurus/docs/reference/configuration.md +++ b/docusaurus/docs/reference/configuration.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Configuration Reference + + **Complete configuration options for all data source types** This reference documents all configuration parameters available when creating `DataSource` instances and initializing the ATT&CK Data Model library. diff --git a/docusaurus/docs/reference/errors.md b/docusaurus/docs/reference/errors.md index 4788c1e2..8c136dff 100644 --- a/docusaurus/docs/reference/errors.md +++ b/docusaurus/docs/reference/errors.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Error Reference + + **Complete error codes, types, and resolution strategies** This reference documents all error types that can occur when using the ATT&CK Data Model library, along with their meanings, common causes, and recommended solutions. diff --git a/docusaurus/docs/reference/index.md b/docusaurus/docs/reference/index.md index 313c3c68..031fd5cd 100644 --- a/docusaurus/docs/reference/index.md +++ b/docusaurus/docs/reference/index.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Reference + + **Complete technical specifications and API documentation** This section provides comprehensive reference material for the ATT&CK Data Model library. All classes, methods, schemas, and configuration options are documented here with precise technical details. diff --git a/docusaurus/docs/reference/schemas/stix-bundle.schema.md b/docusaurus/docs/reference/schemas/stix-bundle.schema.md index 7453ca5c..803fd15d 100644 --- a/docusaurus/docs/reference/schemas/stix-bundle.schema.md +++ b/docusaurus/docs/reference/schemas/stix-bundle.schema.md @@ -1,5 +1,7 @@ # STIX Bundle Schema + + ## StixBundle _Object containing the following properties:_ @@ -14,4 +16,19 @@ _(\*) Required._ ## AttackObjects -_Array of at least 1 [Asset](../reference/schemas/sdo/asset.schema) | [Campaign](../reference/schemas/sdo/campaign.schema) | [Collection](../reference/schemas/sdo/collection.schema) | [DataComponent](../reference/schemas/sdo/data-component.schema) | [DataSource](../reference/schemas/sdo/data-source.schema) | [Group](../reference/schemas/sdo/group.schema) | [Identity](../reference/schemas/sdo/identity.schema) | [Malware](../reference/schemas/sdo/malware.schema) | [Matrix](../reference/schemas/sdo/matrix.schema) | [Mitigation](../reference/schemas/sdo/mitigation.schema) | [Tactic](../reference/schemas/sdo/tactic.schema) | [Technique](../reference/schemas/sdo/technique.schema) | [Tool](../reference/schemas/sdo/tool.schema) | [MarkingDefinition](../reference/schemas/smo/marking-definition.schema) | [Relationship](../reference/schemas/sro/relationship.schema) objects._ +Array of at least 1 +[Asset](sdo/asset.schema) | +[Campaign](sdo/campaign.schema) | +[Collection](sdo/collection.schema) | +[DataComponent](sdo/data-component.schema) | +[DataSource](sdo/data-source.schema) | +[Group](sdo/group.schema) | +[Identity](sdo/identity.schema) | +[Malware](sdo/malware.schema) | +[Matrix](sdo/matrix.schema) | +[Mitigation](sdo/mitigation.schema) | +[Tactic](sdo/tactic.schema) | +[Technique](sdo/technique.schema) | +[Tool](sdo/tool.schema) | +[MarkingDefinition](smo/marking-definition.schema) | +[Relationship](sro/relationship.schema) objects. diff --git a/docusaurus/docs/tutorials/index.md b/docusaurus/docs/tutorials/index.md index 5c9213bc..226c712e 100644 --- a/docusaurus/docs/tutorials/index.md +++ b/docusaurus/docs/tutorials/index.md @@ -1,26 +1,14 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; +import DocCardList from '@theme/DocCardList'; + # Tutorials + + **Learning-oriented guides for getting started with the ATT&CK Data Model** Welcome to the tutorials section! These step-by-step guides will teach you how to work with the MITRE ATT&CK Data Model (ADM) from the ground up. Each tutorial is designed to be completed by anyone with basic TypeScript knowledge, regardless of their familiarity with ATT&CK. -## What You'll Learn - -These tutorials will take you from zero knowledge to confidently using the ATT&CK Data Model in your applications: - -1. **[Your First ATT&CK Query](./your-first-query)** - Learn the basics of loading and exploring ATT&CK data -2. **[Building a Technique Browser](./technique-browser)** - Create a complete application that browses ATT&CK techniques -3. **[Understanding ATT&CK Relationships](./relationships)** - Master navigation between related ATT&CK objects - -## Tutorial Philosophy - -These tutorials follow a hands-on approach where you'll learn by doing: - -- **Step-by-step progression**: Each tutorial builds on the previous one -- **Complete examples**: All code examples are tested and working -- **Immediate results**: Every step produces visible outcomes -- **Beginner-friendly**: No prior ATT&CK knowledge assumed - ## Prerequisites Before starting these tutorials, you should have: @@ -30,22 +18,11 @@ Before starting these tutorials, you should have: - **Terminal/command line** familiarity - A **text editor or IDE** of your choice -## Getting Help - -If you get stuck while following a tutorial: +## Available Tutorials -1. **Double-check each step** - make sure you haven't missed anything -2. **Check the [reference documentation](../reference/)** for detailed API information -3. **Look at the [how-to guides](../how-to-guides/)** for solutions to specific problems -4. **Review the [examples](https://github.com/mitre-attack/attack-data-model/tree/main/examples)** for additional code samples - -## What's Next? - -After completing these tutorials, you'll be ready to: +These tutorials will take you from zero knowledge to confidently using the ATT&CK Data Model in your applications: -- Solve specific problems with our **[How-to Guides](../how-to-guides/)** -- Explore comprehensive API details in the **[Reference](../reference/)** section -- Understand design decisions in the **[Explanation](../explanation/)** section + --- diff --git a/docusaurus/docs/tutorials/multi-domain-analysis.md b/docusaurus/docs/tutorials/multi-domain-analysis.md index 69232e53..c5e41cd2 100644 --- a/docusaurus/docs/tutorials/multi-domain-analysis.md +++ b/docusaurus/docs/tutorials/multi-domain-analysis.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Multi-Domain ATT&CK Analysis + + **Learn to work with Enterprise, Mobile, and ICS ATT&CK domains** In this tutorial, you'll learn how to load and analyze data from multiple ATT&CK domains (Enterprise, Mobile, and ICS). You'll compare techniques across domains and understand how adversary tactics vary between different environments. diff --git a/docusaurus/docs/tutorials/relationships.md b/docusaurus/docs/tutorials/relationships.md index 82d718a1..c34181d2 100644 --- a/docusaurus/docs/tutorials/relationships.md +++ b/docusaurus/docs/tutorials/relationships.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Understanding ATT&CK Relationships + + **Master navigation between related ATT&CK objects** ATT&CK's power comes from the rich relationships between techniques, tactics, groups, software, and mitigations. In this tutorial, you'll learn to navigate these connections to uncover threat intelligence insights and build comprehensive security analysis capabilities. diff --git a/docusaurus/docs/tutorials/technique-browser.md b/docusaurus/docs/tutorials/technique-browser.md index 06c40f08..979f522d 100644 --- a/docusaurus/docs/tutorials/technique-browser.md +++ b/docusaurus/docs/tutorials/technique-browser.md @@ -1,5 +1,9 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Building a Technique Browser + + **Create a complete application that browses ATT&CK techniques** In this tutorial, you'll build a complete command-line application that lets you browse and search ATT&CK techniques interactively. You'll learn to work with multiple object types, implement search functionality, and create a practical tool you can use and extend. diff --git a/docusaurus/docs/tutorials/your-first-query.md b/docusaurus/docs/tutorials/your-first-query.md index 7c6daa9b..23d3e9d3 100644 --- a/docusaurus/docs/tutorials/your-first-query.md +++ b/docusaurus/docs/tutorials/your-first-query.md @@ -1,6 +1,8 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + # Your First ATT&CK Query -**Learn the basics of loading and exploring ATT&CK data** + In this tutorial, you'll learn how to install the ATT&CK Data Model library, load your first ATT&CK dataset, and explore techniques and tactics. By the end, you'll have a working script that can query ATT&CK data and understand the basic concepts of the ATT&CK framework. diff --git a/docusaurus/docusaurus.config.ts b/docusaurus/docusaurus.config.ts index d6f0c9cf..537e1138 100644 --- a/docusaurus/docusaurus.config.ts +++ b/docusaurus/docusaurus.config.ts @@ -39,12 +39,6 @@ const config: Config = { showLastUpdateAuthor: true, showLastUpdateTime: true, }, - // blog: { - // showReadingTime: false, - // path: 'blog', - // routeBasePath: 'known-issues', - // blogTitle: 'Known Issues', - // }, theme: { customCss: './src/css/custom.css', }, @@ -77,11 +71,6 @@ const config: Config = { position: 'left', label: 'Documentation', }, - // { - // to: '/known-issues', - // label: 'Known Issues', - // position: 'left', - // }, { href: 'https://github.com/mitre-attack/attack-data-model', label: 'GitHub', @@ -115,8 +104,8 @@ const config: Config = { to: '/docs/reference/', }, { - label: 'Explanation', - to: '/docs/explanation/', + label: 'Principles', + to: '/docs/principles/', }, ], }, diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index c5be278d..0476fc78 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -10,57 +10,82 @@ const sidebars: SidebarsConfig = { { type: 'doc', id: 'overview', - label: 'Documentation Overview', }, { type: 'category', label: 'Tutorials', description: 'Learning-oriented guides for getting started', + link: { type: 'doc', id: 'tutorials/index' }, items: [ - { - type: 'autogenerated', - dirName: 'tutorials', - }, + 'tutorials/your-first-query', + 'tutorials/technique-browser', + 'tutorials/relationships', + 'tutorials/multi-domain-analysis', ], }, { type: 'category', label: 'How-to Guides', description: 'Problem-oriented solutions for specific tasks', + link: { type: 'doc', id: 'how-to-guides/index' }, items: [ - { - type: 'autogenerated', - dirName: 'how-to-guides', - }, - { - type: 'autogenerated', - dirName: 'how-to-guides/contributing', - }, + 'how-to-guides/manage-data-sources', + 'how-to-guides/validate-bundles', + 'how-to-guides/error-handling', + 'how-to-guides/performance', ], }, { type: 'category', label: 'Reference', description: 'Information-oriented technical specifications', + link: { type: 'doc', id: 'reference/index' }, items: [ - 'reference/index', { type: 'category', label: 'API Documentation', + link: { type: 'doc', id: 'reference/api/index' }, items: [ - { - type: 'autogenerated', - dirName: 'reference/api', - }, + 'reference/api/data-sources', + 'reference/api/attack-data-model', + 'reference/api/utilities', ], }, { type: 'category', label: 'Schema Reference', + link: { type: 'doc', id: 'reference/schemas/index' }, items: [ + 'reference/schemas/stix-bundle.schema', + { + type: 'category', + label: 'STIX Domain Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/sdo', + }, + ], + }, { - type: 'autogenerated', - dirName: 'reference/schemas', + type: 'category', + label: 'STIX Relationship Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/sro', + }, + ], + }, + { + type: 'category', + label: 'STIX Meta Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/smo', + }, + ], }, ], }, @@ -70,13 +95,30 @@ const sidebars: SidebarsConfig = { }, { type: 'category', - label: 'Explanation', + label: 'Principles', description: 'Understanding-oriented context and design rationale', + link: { type: 'doc', id: 'principles/index' }, items: [ - { - type: 'autogenerated', - dirName: 'explanation', - }, + 'principles/why-adm-exists', + 'principles/stix-foundation', + 'principles/attack-specification-overview', + 'principles/object-design-rationale', + 'principles/versioning-philosophy', + 'principles/schema-design', + 'principles/compatibility', + 'principles/trade-offs', + ], + }, + { + type: 'category', + label: 'Contributing', + description: 'How to contribute to the project', + link: { type: 'doc', id: 'contributing/index' }, + items: [ + 'contributing/dev-setup', + 'contributing/coding-style', + 'contributing/tests', + 'contributing/docs', ], }, ], diff --git a/docusaurus/src/components/DocTypeIndicator/index.tsx b/docusaurus/src/components/DocTypeIndicator/index.tsx index efc3c2f1..9156c8d8 100644 --- a/docusaurus/src/components/DocTypeIndicator/index.tsx +++ b/docusaurus/src/components/DocTypeIndicator/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import clsx from 'clsx'; import styles from './styles.module.css'; -export type DocType = 'tutorial' | 'how-to' | 'reference' | 'explanation'; +export type DocType = 'tutorial' | 'how-to' | 'reference' | 'principles'; export interface DocTypeIndicatorProps { type: DocType; @@ -33,9 +33,9 @@ const docTypeConfig = { color: '#3b82f6', // blue-500 bgColor: '#dbeafe', // blue-100 }, - explanation: { + principles: { emoji: '💡', - label: 'Explanation', + label: 'Principles', description: 'Understanding-oriented', color: '#8b5cf6', // violet-500 bgColor: '#ede9fe', // violet-100 diff --git a/docusaurus/src/components/HomepageFeatures/index.tsx b/docusaurus/src/components/HomepageFeatures/index.tsx index 7a9843da..3a677457 100644 --- a/docusaurus/src/components/HomepageFeatures/index.tsx +++ b/docusaurus/src/components/HomepageFeatures/index.tsx @@ -23,28 +23,28 @@ const documentationTypes = [ href: 'docs/reference/', }, { - type: 'explanation' as const, - title: 'Explanation', + type: 'principles' as const, + title: 'Principles', description: 'Deep dives into the architecture, design philosophy, and trade-offs that shape the library.', - href: 'docs/explanation/', + href: 'docs/principles/', }, ]; const keyFeatures = [ { - title: '🔒 Type-Safe', + title: 'Type-Safe', description: 'Full TypeScript support with compile-time validation and IntelliSense.', }, { - title: '✅ STIX Compliant', + title: 'STIX Compliant', description: 'Built on STIX 2.1 standards for seamless threat intelligence integration.', }, { - title: '🔗 Rich Relationships', + title: 'Rich Relationships', description: 'Intuitive navigation between techniques, tactics, groups, and more.', }, { - title: '📊 Multi-Domain', + title: 'Multi-Domain', description: 'Supports Enterprise, Mobile, and ICS ATT&CK domains.', }, ]; @@ -68,44 +68,29 @@ export default function HomepageFeatures(): JSX.Element {
- Choose Your Documentation Path + Let's Get Started

- Our documentation aims to provide exactly the right information for your needs. - Let us know if anything is missing! + This is the official documentation for the ATT&CK Data Model library.

-
- {documentationTypes.map((docType, idx) => ( -
- +
+
+
+
- ))} -
-
- - - {/* Key Features Section */} -
-
-
- - Library Features - -

- Built for type safety, standards compliance, and developer experience -

-
-
- {keyFeatures.map((feature, idx) => ( - - ))} +
+ +
+
+
+
+ +
+
+ +
+
diff --git a/docusaurus/src/components/HomepageFeatures/styles.module.css b/docusaurus/src/components/HomepageFeatures/styles.module.css index 91b464c7..ecdfcb54 100644 --- a/docusaurus/src/components/HomepageFeatures/styles.module.css +++ b/docusaurus/src/components/HomepageFeatures/styles.module.css @@ -4,14 +4,10 @@ background: var(--ifm-background-surface-color); } -.featuresSection { - padding: 4rem 0; - background: var(--ifm-color-emphasis-100); -} - .quickStartSection { padding: 4rem 0; - background: var(--ifm-background-surface-color); + /* background: var(--ifm-background-surface-color); */ + background: var(--ifm-color-emphasis-100); } /* Common section elements */ @@ -137,10 +133,6 @@ } /* Dark theme adjustments */ -[data-theme='dark'] .featuresSection { - background: var(--ifm-color-emphasis-200); -} - [data-theme='dark'] .keyFeatureContent { background: var(--ifm-background-color); border-color: var(--ifm-color-emphasis-300); @@ -162,7 +154,6 @@ } .documentationSection, - .featuresSection, .quickStartSection { padding: 3rem 0; } @@ -186,7 +177,6 @@ } .documentationSection, - .featuresSection, .quickStartSection { padding: 2rem 0; } diff --git a/docusaurus/src/components/WorkInProgressNotice.tsx b/docusaurus/src/components/WorkInProgressNotice.tsx new file mode 100644 index 00000000..b259f8b2 --- /dev/null +++ b/docusaurus/src/components/WorkInProgressNotice.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import Admonition from '@theme/Admonition'; + +export default function WorkInProgressNotice() { + return ( + + This document is a work in progress. Content may change, and some sections may be incomplete. + + ); +} diff --git a/package.json b/package.json index 4eb3985d..175a6f12 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,7 @@ "lint:fix": "npm run lint -- --fix", "prettier": "npx prettier src --check", "prettier:fix": "npm run prettier -- --write", - "format": "npm run prettier:fix && npm run lint:fix", - "validate-bundles": "npx tsx scripts/validate-prod-stix21-bundles.ts", - "validate-local-bundle": "npx tsx scripts/validate-local-stix21-bundle.ts" + "format": "npm run prettier:fix && npm run lint:fix" }, "contributors": [ { From 670ab0ff139595a7ce9ea59d704dfad48092a141 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 19 Aug 2025 14:55:58 -0500 Subject: [PATCH 30/39] docs: remove trailing content on doc pages --- docusaurus/docs/contributing/coding-style.md | 2 + docusaurus/docs/contributing/dev-setup.md | 2 + docusaurus/docs/contributing/docs.md | 2 + docusaurus/docs/contributing/index.md | 2 + docusaurus/docs/contributing/tests.md | 2 + .../docs/how-to-guides/error-handling.md | 8 - docusaurus/docs/how-to-guides/index.md | 2 - .../docs/how-to-guides/manage-data-sources.md | 2 - docusaurus/docs/how-to-guides/performance.md | 2 - .../docs/how-to-guides/validate-bundles.md | 8 - .../attack-specification-overview.md | 2 + docusaurus/docs/principles/compatibility.md | 8 - docusaurus/docs/principles/index.md | 156 +----------------- .../principles/object-design-rationale.md | 13 -- docusaurus/docs/principles/schema-design.md | 2 - docusaurus/docs/principles/stix-foundation.md | 13 -- docusaurus/docs/principles/trade-offs.md | 2 + .../docs/principles/versioning-philosophy.md | 12 -- docusaurus/docs/principles/why-adm-exists.md | 10 -- docusaurus/docs/principles/why-zod.md | 26 +++ .../docs/reference/api/attack-data-model.md | 6 - docusaurus/docs/reference/api/data-sources.md | 6 - docusaurus/docs/reference/api/index.md | 6 +- docusaurus/docs/reference/api/utilities.md | 6 - docusaurus/docs/reference/configuration.md | 6 - docusaurus/docs/reference/errors.md | 6 - docusaurus/docs/reference/index.md | 2 - docusaurus/docs/tutorials/index.md | 2 - .../docs/tutorials/multi-domain-analysis.md | 2 - docusaurus/docs/tutorials/relationships.md | 2 - .../docs/tutorials/technique-browser.md | 2 - docusaurus/docs/tutorials/your-first-query.md | 2 - docusaurus/sidebars.ts | 1 + docusaurus/src/css/custom.css | 3 +- docusaurus/src/pages/index.tsx | 1 - 35 files changed, 46 insertions(+), 283 deletions(-) create mode 100644 docusaurus/docs/principles/why-zod.md diff --git a/docusaurus/docs/contributing/coding-style.md b/docusaurus/docs/contributing/coding-style.md index 09f2120d..a706720c 100644 --- a/docusaurus/docs/contributing/coding-style.md +++ b/docusaurus/docs/contributing/coding-style.md @@ -42,3 +42,5 @@ Prefix your commit with one of the following: The library is compiled with `"strict": true` and imports must be path-alias aware (`@/…`). Run `npm run build` to catch any type errors locally. + +--- diff --git a/docusaurus/docs/contributing/dev-setup.md b/docusaurus/docs/contributing/dev-setup.md index 0f13ab1c..f27424fc 100644 --- a/docusaurus/docs/contributing/dev-setup.md +++ b/docusaurus/docs/contributing/dev-setup.md @@ -31,3 +31,5 @@ npm run test # vitest tests You are now ready to hack on the library. Continue with [Coding Style & Linting](coding-style.md) for the mandatory style rules. + +--- diff --git a/docusaurus/docs/contributing/docs.md b/docusaurus/docs/contributing/docs.md index 7357813c..b83595bb 100644 --- a/docusaurus/docs/contributing/docs.md +++ b/docusaurus/docs/contributing/docs.md @@ -39,3 +39,5 @@ Its pretty easy to add a new guide. Basically just do the following: 1. Create your Markdown file inside `docusaurus/docs//my-guide.md` 2. Update `sidebars.ts` to include it + +--- diff --git a/docusaurus/docs/contributing/index.md b/docusaurus/docs/contributing/index.md index 6fa35dba..32f3b110 100644 --- a/docusaurus/docs/contributing/index.md +++ b/docusaurus/docs/contributing/index.md @@ -10,3 +10,5 @@ It complements the high-level `CONTRIBUTING.md` in the root of the repository by ## Topics + +--- diff --git a/docusaurus/docs/contributing/tests.md b/docusaurus/docs/contributing/tests.md index 74e2fe66..166b5e16 100644 --- a/docusaurus/docs/contributing/tests.md +++ b/docusaurus/docs/contributing/tests.md @@ -38,3 +38,5 @@ When adding new schemas or refinement logic: 1. Write **positive** test cases (`schema.parse` passes) 2. Write **negative** test cases that assert specific error messages + +--- diff --git a/docusaurus/docs/how-to-guides/error-handling.md b/docusaurus/docs/how-to-guides/error-handling.md index 2e08d45c..f68f09c2 100644 --- a/docusaurus/docs/how-to-guides/error-handling.md +++ b/docusaurus/docs/how-to-guides/error-handling.md @@ -698,11 +698,3 @@ main().catch(console.error); ``` --- - -## Next Steps - -- **Bundle Validation**: Learn how to [validate custom STIX bundles](./validate-bundles) before processing -- **Schema Extension**: See how to [extend schemas with custom fields](./extend-schemas) for enhanced error detection -- **Reference**: Check the [complete API documentation](../reference/) for error handling methods - -Your application now handles errors gracefully and provides reliable ATT&CK data access even in challenging environments! diff --git a/docusaurus/docs/how-to-guides/index.md b/docusaurus/docs/how-to-guides/index.md index c25249a2..b341e54b 100644 --- a/docusaurus/docs/how-to-guides/index.md +++ b/docusaurus/docs/how-to-guides/index.md @@ -14,5 +14,3 @@ These guides provide direct solutions to common problems you'll encounter when w --- - -**Can't find what you're looking for?** Consider [opening a GitHub Issue](https://github.com/mitre-attack/attack-data-model/issues/new) to request a new how-to guide. diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.md b/docusaurus/docs/how-to-guides/manage-data-sources.md index c7a1088a..df79d617 100644 --- a/docusaurus/docs/how-to-guides/manage-data-sources.md +++ b/docusaurus/docs/how-to-guides/manage-data-sources.md @@ -403,5 +403,3 @@ async function loadWithMonitoring(config: any) { - [Performance Optimization](./performance) - Scale for large datasets --- - -**You're now equipped to manage ATT&CK data sources effectively in any environment!** diff --git a/docusaurus/docs/how-to-guides/performance.md b/docusaurus/docs/how-to-guides/performance.md index 319863ef..76269737 100644 --- a/docusaurus/docs/how-to-guides/performance.md +++ b/docusaurus/docs/how-to-guides/performance.md @@ -534,5 +534,3 @@ const config = { - [Reference: Configuration](../reference/configuration) - All configuration options --- - -**You're now equipped to scale the ATT&CK Data Model for production workloads!** diff --git a/docusaurus/docs/how-to-guides/validate-bundles.md b/docusaurus/docs/how-to-guides/validate-bundles.md index c72b0bb5..6d15f8c4 100644 --- a/docusaurus/docs/how-to-guides/validate-bundles.md +++ b/docusaurus/docs/how-to-guides/validate-bundles.md @@ -407,11 +407,3 @@ echo "✅ All bundles validated successfully" ``` --- - -## Next Steps - -- **Error Handling**: Learn [how to handle parsing errors gracefully](./error-handling) -- **Schema Extension**: See [how to extend schemas with custom fields](./extend-schemas) -- **Reference**: Check the [complete API documentation](../reference/) - -Your custom STIX bundles should now validate correctly and load reliably in the ATT&CK Data Model! diff --git a/docusaurus/docs/principles/attack-specification-overview.md b/docusaurus/docs/principles/attack-specification-overview.md index 63b5a35b..1b4b46a8 100644 --- a/docusaurus/docs/principles/attack-specification-overview.md +++ b/docusaurus/docs/principles/attack-specification-overview.md @@ -271,3 +271,5 @@ Working code examples demonstrate specification usage patterns. #### "The specification is static" **Reality**: The specification evolves continuously with new object types, properties, and validation rules. + +--- diff --git a/docusaurus/docs/principles/compatibility.md b/docusaurus/docs/principles/compatibility.md index 5d5161b9..eea1f735 100644 --- a/docusaurus/docs/principles/compatibility.md +++ b/docusaurus/docs/principles/compatibility.md @@ -239,11 +239,3 @@ const detectsBy = technique.x_mitre_data_sources || 4. **Plan Resources**: Budget time and resources for regular compatibility updates --- - -Understanding compatibility relationships helps you build robust applications that can evolve alongside the ATT&CK ecosystem. By planning for version changes and implementing defensive coding practices, you can maintain stable functionality while taking advantage of new capabilities as they become available. - -**Related Topics**: - -- [Versioning Philosophy](./versioning-philosophy) - Deep dive into ATT&CK's multi-dimensional versioning -- [Configuration Reference](../reference/configuration) - All supported configuration options -- [Error Handling Guide](../how-to-guides/error-handling) - Handle compatibility and parsing errors diff --git a/docusaurus/docs/principles/index.md b/docusaurus/docs/principles/index.md index 5712cd54..6cd46753 100644 --- a/docusaurus/docs/principles/index.md +++ b/docusaurus/docs/principles/index.md @@ -1,5 +1,4 @@ import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; -import { DocTypeCard } from '@site/src/components/DocTypeIndicator'; # Guiding Principles @@ -13,24 +12,9 @@ This section explores the "why" behind the ATT&CK Data Model - the design decisi ### Foundational Context -
-
- -
-
- -
-
+- **[Why the ATT&CK Data Model Exists](./why-adm-exists)** - The problem context, solution approach, and value proposition +- **[Why Zod Instead of X](./why-zod)** - Why we chose Zod over other options +- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX was chosen and how it shapes the architecture ### ATT&CK Specification Understanding @@ -47,138 +31,4 @@ This section explores the "why" behind the ATT&CK Data Model - the design decisi - **[Compatibility](./compatibility)** - Compatibility matrix of versions of ATT&CK, the data model, and the library - **[Architecture & Design Trade-offs](./trade-offs)** - Reasons why architecture decisions were made -## Core Principles - -The ATT&CK Data Model is built on several key principles that influence all design decisions: - -### STIX 2.1 Compliance First - -- **Principle**: Maintain strict compatibility with STIX 2.1 specification -- **Implication**: All ATT&CK objects are valid STIX objects first, ATT&CK objects second -- **Trade-off**: Sometimes requires more complex APIs to maintain standard compliance - -**Example**: Using STIX relationship objects instead of embedded references, even though embedded references might be more convenient for some use cases. - -### Type Safety Without Performance Cost - -- **Principle**: Provide strong TypeScript types while maintaining runtime performance -- **Implication**: Heavy use of Zod schemas for both validation and type inference -- **Trade-off**: Larger bundle size in exchange for compile-time safety and runtime validation - -**Example**: Every ATT&CK object has both a Zod schema (for validation) and TypeScript interface (for typing), even though this creates some duplication. - -### Relationship-First Navigation - -- **Principle**: ATT&CK's value comes from object relationships, so navigation must be intuitive -- **Implication**: Implementation classes provide relationship methods that abstract STIX relationship complexity -- **Trade-off**: Memory overhead for relationship indexing in exchange for fast navigation - -**Example**: `technique.getTactics()` abstracts the underlying STIX relationships and provides immediate access to related objects. - -### Extensibility Through Standards - -- **Principle**: Support customization without breaking compatibility -- **Implication**: Extensions follow STIX custom property conventions -- **Trade-off**: More verbose extension syntax but guaranteed interoperability - -**Example**: Custom fields use `x_custom_` prefixes and require manual refinement reapplication, following STIX best practices. - -## Architectural Themes - -### Layered Architecture - -The library uses a three-layer architecture: - -1. **Schema Layer** - Zod schemas for validation and type inference -2. **Class Layer** - Implementation classes with relationship navigation -3. **Data Access Layer** - Data sources and loading mechanisms - -Each layer serves distinct purposes and can be used independently or together. - -### Immutable Data Model - -- **Decision**: ATT&CK objects are immutable after loading -- **Rationale**: Prevents accidental modification of shared threat intelligence data -- **Implication**: Any modifications require creating new objects or data models - -### Lazy Relationship Resolution - -- **Decision**: Relationships are resolved on-demand rather than eagerly -- **Rationale**: Better memory usage and faster initial loading -- **Implication**: First access to relationships may be slightly slower than subsequent accesses - -## Design Evolution - -### Historical Context - -The ATT&CK Data Model evolved through several design phases: - -1. **Simple JSON Processing** - Direct JSON manipulation without validation -2. **Schema-First Design** - Introduction of Zod validation schemas -3. **Relationship Navigation** - Addition of implementation classes with navigation methods -4. **Multi-Source Support** - Extensible data source architecture -5. **Type Safety** - Full TypeScript integration with proper type inference - -Each evolution maintained backward compatibility while addressing real-world usage patterns. - -### Current Design State - -The current architecture reflects lessons learned from: - -- **Enterprise deployments** requiring strict validation -- **Research applications** needing flexible data exploration -- **Integration scenarios** demanding standards compliance -- **Performance requirements** in high-throughput systems - -## Understanding Through Examples - -### Why Zod Instead of JSON Schema? - -- **Context**: Need for runtime validation and TypeScript integration -- **Decision**: Use Zod for schema definition -- **Alternatives Considered**: JSON Schema, Joi, Yup - -**Rationale**: - -- Zod provides TypeScript type inference automatically -- Runtime validation matches compile-time types exactly -- Schema definitions are more maintainable in TypeScript -- Better error messages for developers - -**Trade-offs**: - -- Zod is less universally known than JSON Schema -- Schemas are tied to TypeScript ecosystem -- Larger runtime bundle due to Zod dependency - -### Why Implementation Classes Instead of Plain Objects? - -- **Context**: Need for relationship navigation without breaking STIX compliance -- **Decision**: Create implementation classes that extend plain objects with methods -- **Alternatives Considered**: Plain objects with utility functions, custom object shapes - -**Rationale**: - -- Object-oriented navigation is more intuitive (`technique.getTactics()`) -- Encapsulation keeps relationship logic organized -- TypeScript provides better IntelliSense for methods -- Classes can be extended by library users - -**Trade-offs**: - -- Increased memory usage due to method inheritance -- More complex object construction process -- Learning curve for users expecting plain JSON objects - -## Discussion and Feedback - -These explanations reflect the current understanding and rationale behind design decisions. -If you have questions about specific choices or suggestions for different approaches: - -- **Open a GitHub Discussion** for architectural questions -- **Create an Issue** for specific design concerns -- **Join the community** to discuss alternative approaches - --- - -**Ready to dive deeper?** Start with **[Why the ATT&CK Data Model Exists](./why-adm-exists)** to understand the foundational context that shapes everything else. diff --git a/docusaurus/docs/principles/object-design-rationale.md b/docusaurus/docs/principles/object-design-rationale.md index c2033812..4ff47635 100644 --- a/docusaurus/docs/principles/object-design-rationale.md +++ b/docusaurus/docs/principles/object-design-rationale.md @@ -446,16 +446,3 @@ Understanding these design rationales helps you: - **Evaluate trade-offs** when extending the framework --- - -## The Design Philosophy in Practice - -Object design decisions reflect a consistent philosophy that prioritizes: - -1. **Standards compliance over convenience** -2. **Semantic accuracy over simplicity** -3. **Long-term sustainability over short-term optimization** -4. **Community interoperability over isolated efficiency** - -Understanding this philosophy helps you work effectively with ATT&CK objects and contribute meaningfully to the framework's evolution. - -**Next**: Explore how **[Identifier Systems](./identifier-systems)** enable both machine processing and human comprehension of ATT&CK data. diff --git a/docusaurus/docs/principles/schema-design.md b/docusaurus/docs/principles/schema-design.md index 4bcea19c..26dfcf3d 100644 --- a/docusaurus/docs/principles/schema-design.md +++ b/docusaurus/docs/principles/schema-design.md @@ -478,5 +478,3 @@ The schema design reflects a philosophical commitment to: Understanding these philosophical commitments helps you work effectively with the schema layer and contribute to its continued development. --- - -**Next**: Explore how these schema design decisions create **[Performance vs Flexibility Trade-offs](./trade-offs)** in the overall library architecture. diff --git a/docusaurus/docs/principles/stix-foundation.md b/docusaurus/docs/principles/stix-foundation.md index 5c66b21d..dcfed435 100644 --- a/docusaurus/docs/principles/stix-foundation.md +++ b/docusaurus/docs/principles/stix-foundation.md @@ -443,16 +443,3 @@ STIX provides a stable foundation as ATT&CK specifications evolve: - **Version Management**: STIX versioning handles ATT&CK specification updates --- - -## Understanding the Foundation's Impact - -The STIX foundation isn't just a data format choice - it's an architectural philosophy that prioritizes: - -1. **Standards compliance** over proprietary optimization -2. **Interoperability** over convenience -3. **Long-term sustainability** over short-term simplicity -4. **Community ecosystem** over isolated solutions - -This philosophy shapes every design decision in the ATT&CK Data Model. Understanding it helps you work effectively with the library and contribute to its evolution. - -**Next**: Explore how this foundation enables **[Schema Design Principles](./schema-design)** that balance STIX compliance with usability. diff --git a/docusaurus/docs/principles/trade-offs.md b/docusaurus/docs/principles/trade-offs.md index e4b3a956..86449a8b 100644 --- a/docusaurus/docs/principles/trade-offs.md +++ b/docusaurus/docs/principles/trade-offs.md @@ -222,3 +222,5 @@ We decided to make use of Semantic Versioning with Major Version Breaking Change - Consider single-domain optimization if working with specific domains Understanding these trade-offs helps you make informed decisions about whether the ATT&CK Data Model's approach aligns with your specific requirements and constraints. + +--- diff --git a/docusaurus/docs/principles/versioning-philosophy.md b/docusaurus/docs/principles/versioning-philosophy.md index 97ee1943..cbc1a488 100644 --- a/docusaurus/docs/principles/versioning-philosophy.md +++ b/docusaurus/docs/principles/versioning-philosophy.md @@ -507,15 +507,3 @@ if (semver.lt(obj.x_mitre_attack_spec_version, MIN_SPEC_VERSION)) { ``` --- - -## The Philosophy in Practice - -ATT&CK's multi-dimensional versioning reflects the reality that complex systems evolve along multiple independent axes. Rather than forcing artificial synchronization that would slow all evolution to the pace of the slowest component, the framework provides precise tracking for each type of change: - -- **STIX versions** track standards compliance -- **ATT&CK specification versions** track feature availability -- **Object versions** track content evolution - -Understanding this philosophy helps you build robust applications that can handle version complexity gracefully and evolve alongside the ATT&CK framework. - -**Next**: Return to the **[Explanation Overview](./)** to explore other aspects of ATT&CK Data Model architecture and design decisions. diff --git a/docusaurus/docs/principles/why-adm-exists.md b/docusaurus/docs/principles/why-adm-exists.md index c649d0f1..bb939f6f 100644 --- a/docusaurus/docs/principles/why-adm-exists.md +++ b/docusaurus/docs/principles/why-adm-exists.md @@ -272,13 +272,3 @@ The ATT&CK Data Model's success is measured by: - **Industry standardization** around common ATT&CK data patterns --- - -## Next Steps in Understanding - -Now that you understand why the ATT&CK Data Model exists, explore how it achieves these goals: - -- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX compliance drives architectural decisions -- **[Schema Design Principles](./schema-design)** - How validation and type safety are implemented -- **[Performance vs Flexibility Trade-offs](./trade-offs)** - The balance between speed and usability - -The problem context shapes every design decision in the library. Understanding this context helps you use the library effectively and contribute to its continued evolution. diff --git a/docusaurus/docs/principles/why-zod.md b/docusaurus/docs/principles/why-zod.md new file mode 100644 index 00000000..1b7f5988 --- /dev/null +++ b/docusaurus/docs/principles/why-zod.md @@ -0,0 +1,26 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Why Zod Instead of X + + + +## JSON Schema + +- **Context**: Need for runtime validation and TypeScript integration +- **Decision**: Use Zod for schema definition +- **Alternatives Considered**: JSON Schema, Pydantic (when we were considering using Python) + +**Rationale**: + +- Zod provides TypeScript type inference automatically +- Runtime validation matches compile-time types exactly +- Schema definitions are more maintainable in TypeScript +- Better error messages for developers + +**Trade-offs**: + +- Zod is less universally known than JSON Schema +- Schemas are tied to TypeScript ecosystem +- Larger runtime bundle due to Zod dependency + +--- diff --git a/docusaurus/docs/reference/api/attack-data-model.md b/docusaurus/docs/reference/api/attack-data-model.md index 4efb19a6..025e0903 100644 --- a/docusaurus/docs/reference/api/attack-data-model.md +++ b/docusaurus/docs/reference/api/attack-data-model.md @@ -302,9 +302,3 @@ const highValueTargets = attackDataModel.techniques.filter(t => { ``` --- - -## See Also - -- **[DataSource](./data-sources)** - Data source configuration and loading -- **[Implementation Classes](../schemas/)** - Individual object class documentation -- **[Relationships](../schemas/sro/relationship.schema)** - Relationship type specifications diff --git a/docusaurus/docs/reference/api/data-sources.md b/docusaurus/docs/reference/api/data-sources.md index 06c90204..7a81b017 100644 --- a/docusaurus/docs/reference/api/data-sources.md +++ b/docusaurus/docs/reference/api/data-sources.md @@ -423,9 +423,3 @@ const models = registrations ``` --- - -## See Also - -- **[AttackDataModel](./attack-data-model)** - Working with loaded data models -- **[Configuration Reference](../configuration)** - Complete configuration options -- **[Error Reference](../errors)** - Error handling and troubleshooting diff --git a/docusaurus/docs/reference/api/index.md b/docusaurus/docs/reference/api/index.md index a0197770..d6e70656 100644 --- a/docusaurus/docs/reference/api/index.md +++ b/docusaurus/docs/reference/api/index.md @@ -169,8 +169,4 @@ The library is designed for optimal TypeScript experience: **IDE Support:** Auto-completion, inline documentation, and error detection **Generic Support:** Type parameters for custom extensions and filtering -## Next Steps - -- Browse the [Schema Reference](../schemas/) for detailed field documentation -- Check the [Configuration Reference](../configuration) for setup options -- See [How-to Guides](../../how-to-guides/) for practical implementation examples +--- diff --git a/docusaurus/docs/reference/api/utilities.md b/docusaurus/docs/reference/api/utilities.md index 4a5c983f..52986f9c 100644 --- a/docusaurus/docs/reference/api/utilities.md +++ b/docusaurus/docs/reference/api/utilities.md @@ -596,9 +596,3 @@ fs.writeFileSync('techniques-basic.csv', basicTechniquesCsv); ``` --- - -## See Also - -- **[AttackDataModel](./attack-data-model)** - Main data model class -- **[DataSource](./data-sources)** - Data source configuration -- **[Error Reference](../errors)** - Error types and handling diff --git a/docusaurus/docs/reference/configuration.md b/docusaurus/docs/reference/configuration.md index 2e39c354..9b6de9e8 100644 --- a/docusaurus/docs/reference/configuration.md +++ b/docusaurus/docs/reference/configuration.md @@ -522,9 +522,3 @@ const newConfig = new DataSource({ | `ATT_VERSION` | *(removed)* | Now per-DataSource configuration | --- - -## See Also - -- **[DataSource API](./api/data-sources)** - DataSource class documentation -- **[Error Reference](./errors)** - Configuration error troubleshooting -- **[How-to Guide: Error Handling](../how-to-guides/error-handling)** - Robust configuration patterns diff --git a/docusaurus/docs/reference/errors.md b/docusaurus/docs/reference/errors.md index 8c136dff..37751f47 100644 --- a/docusaurus/docs/reference/errors.md +++ b/docusaurus/docs/reference/errors.md @@ -385,9 +385,3 @@ console.log(`Object counts:`, status.objectCounts); 4. **Update data sources to fix reference issues** --- - -## See Also - -- **[Configuration Reference](./configuration)** - Complete configuration options -- **[How-to Guide: Error Handling](../how-to-guides/error-handling)** - Practical error handling strategies -- **[Validation Guide](../how-to-guides/validate-bundles)** - Data validation techniques diff --git a/docusaurus/docs/reference/index.md b/docusaurus/docs/reference/index.md index 031fd5cd..245bfdce 100644 --- a/docusaurus/docs/reference/index.md +++ b/docusaurus/docs/reference/index.md @@ -190,5 +190,3 @@ This reference documentation follows these principles: **Looking for something specific?** Use the search functionality or check the relevant section above. --- - -**Need more context?** Visit the [Explanation](../explanation/) section for design rationale and architectural details. diff --git a/docusaurus/docs/tutorials/index.md b/docusaurus/docs/tutorials/index.md index 226c712e..75be3b06 100644 --- a/docusaurus/docs/tutorials/index.md +++ b/docusaurus/docs/tutorials/index.md @@ -25,5 +25,3 @@ These tutorials will take you from zero knowledge to confidently using the ATT&C --- - -**Ready to get started?** Begin with **[Your First ATT&CK Query](./your-first-query)** to load your first ATT&CK dataset! diff --git a/docusaurus/docs/tutorials/multi-domain-analysis.md b/docusaurus/docs/tutorials/multi-domain-analysis.md index c5e41cd2..efe8e06c 100644 --- a/docusaurus/docs/tutorials/multi-domain-analysis.md +++ b/docusaurus/docs/tutorials/multi-domain-analysis.md @@ -282,5 +282,3 @@ Now that you understand cross-domain analysis, explore: **Version consistency**: Use the same version number across all domains to ensure compatibility in cross-domain comparisons. --- - -**Excellent work!** You now understand how to work with multiple ATT&CK domains and can perform comprehensive cross-domain threat analysis. diff --git a/docusaurus/docs/tutorials/relationships.md b/docusaurus/docs/tutorials/relationships.md index c34181d2..6ef73148 100644 --- a/docusaurus/docs/tutorials/relationships.md +++ b/docusaurus/docs/tutorials/relationships.md @@ -431,5 +431,3 @@ You've completed all tutorials! Now you can: **Missing relationships**: Remember that ATT&CK data evolves. Some relationships may not exist in all versions or domains. --- - -**Congratulations!** You've mastered ATT&CK relationship navigation and completed the tutorial series. You now have the skills to build sophisticated security analysis tools and conduct advanced threat intelligence research! diff --git a/docusaurus/docs/tutorials/technique-browser.md b/docusaurus/docs/tutorials/technique-browser.md index 979f522d..3f9de828 100644 --- a/docusaurus/docs/tutorials/technique-browser.md +++ b/docusaurus/docs/tutorials/technique-browser.md @@ -448,5 +448,3 @@ You're now ready for more advanced topics: - **[Reference Documentation](../reference/)** - Explore the complete API --- - -**Excellent work!** You've built a complete ATT&CK technique browser and learned essential skills for working with ATT&CK data programmatically. diff --git a/docusaurus/docs/tutorials/your-first-query.md b/docusaurus/docs/tutorials/your-first-query.md index 23d3e9d3..8d820a61 100644 --- a/docusaurus/docs/tutorials/your-first-query.md +++ b/docusaurus/docs/tutorials/your-first-query.md @@ -202,5 +202,3 @@ Or explore other documentation: **TypeScript compilation errors**: Make sure you're using `npx tsx` to run TypeScript directly, or compile with `npx tsc` first. --- - -**Great job!** You've completed your first ATT&CK query and learned the fundamental concepts. Ready for the next challenge? diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index 0476fc78..d240e490 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -100,6 +100,7 @@ const sidebars: SidebarsConfig = { link: { type: 'doc', id: 'principles/index' }, items: [ 'principles/why-adm-exists', + 'principles/why-zod', 'principles/stix-foundation', 'principles/attack-specification-overview', 'principles/object-design-rationale', diff --git a/docusaurus/src/css/custom.css b/docusaurus/src/css/custom.css index b2be69fc..8fc91ec4 100644 --- a/docusaurus/src/css/custom.css +++ b/docusaurus/src/css/custom.css @@ -203,7 +203,6 @@ blockquote p:last-child { /* Improved navbar styling */ .navbar { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); - backdrop-filter: blur(8px); } /* Enhanced sidebar styling */ @@ -313,4 +312,4 @@ blockquote p:last-child { :focus-visible { outline: 2px solid var(--ifm-color-primary); outline-offset: 2px; -} \ No newline at end of file +} diff --git a/docusaurus/src/pages/index.tsx b/docusaurus/src/pages/index.tsx index 66666837..438c59f7 100644 --- a/docusaurus/src/pages/index.tsx +++ b/docusaurus/src/pages/index.tsx @@ -14,7 +14,6 @@ function HomepageHeader() {

MITRE ATT&CK® Data Model

-

Documentation

From 2be12099519039fecb7fd54f9157993f32ac76c4 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 19 Aug 2025 16:38:24 -0500 Subject: [PATCH 31/39] docs: rename md files to mdx --- docs/SPEC.md | 75 +- .../{coding-style.md => coding-style.mdx} | 0 .../{dev-setup.md => dev-setup.mdx} | 2 +- .../docs/contributing/{docs.md => docs.mdx} | 0 .../docs/contributing/{index.md => index.mdx} | 0 .../docs/contributing/{tests.md => tests.mdx} | 0 .../{error-handling.md => error-handling.mdx} | 0 .../how-to-guides/{index.md => index.mdx} | 0 ...ata-sources.md => manage-data-sources.mdx} | 0 .../{performance.md => performance.mdx} | 0 ...lidate-bundles.md => validate-bundles.mdx} | 0 docusaurus/docs/{index.md => index.mdx} | 0 docusaurus/docs/{overview.md => overview.mdx} | 0 ...w.md => attack-specification-overview.mdx} | 0 .../{compatibility.md => compatibility.mdx} | 0 .../docs/principles/{index.md => index.mdx} | 3 +- ...tionale.md => object-design-rationale.mdx} | 0 .../{schema-design.md => schema-design.mdx} | 0 ...stix-foundation.md => stix-foundation.mdx} | 0 .../{trade-offs.md => trade-offs.mdx} | 0 ...hilosophy.md => versioning-philosophy.mdx} | 0 .../{why-adm-exists.md => why-adm-exists.mdx} | 0 .../principles/{why-zod.md => why-zod.mdx} | 0 ...ck-data-model.md => attack-data-model.mdx} | 0 .../api/{data-sources.md => data-sources.mdx} | 0 .../reference/api/{index.md => index.mdx} | 0 .../api/{utilities.md => utilities.mdx} | 0 .../{configuration.md => configuration.mdx} | 0 .../docs/reference/{errors.md => errors.mdx} | 0 .../docs/reference/{index.md => index.mdx} | 0 ...undle.schema.md => stix-bundle.schema.mdx} | 0 docusaurus/package-lock.json | 2180 +++++++++++++++- docusaurus/package.json | 2 + eslint.config.js | 16 + generate-docs.sh | 6 +- package-lock.json | 2245 ++++++++++++++++- package.json | 2 + 37 files changed, 4351 insertions(+), 180 deletions(-) rename docusaurus/docs/contributing/{coding-style.md => coding-style.mdx} (100%) rename docusaurus/docs/contributing/{dev-setup.md => dev-setup.mdx} (90%) rename docusaurus/docs/contributing/{docs.md => docs.mdx} (100%) rename docusaurus/docs/contributing/{index.md => index.mdx} (100%) rename docusaurus/docs/contributing/{tests.md => tests.mdx} (100%) rename docusaurus/docs/how-to-guides/{error-handling.md => error-handling.mdx} (100%) rename docusaurus/docs/how-to-guides/{index.md => index.mdx} (100%) rename docusaurus/docs/how-to-guides/{manage-data-sources.md => manage-data-sources.mdx} (100%) rename docusaurus/docs/how-to-guides/{performance.md => performance.mdx} (100%) rename docusaurus/docs/how-to-guides/{validate-bundles.md => validate-bundles.mdx} (100%) rename docusaurus/docs/{index.md => index.mdx} (100%) rename docusaurus/docs/{overview.md => overview.mdx} (100%) rename docusaurus/docs/principles/{attack-specification-overview.md => attack-specification-overview.mdx} (100%) rename docusaurus/docs/principles/{compatibility.md => compatibility.mdx} (100%) rename docusaurus/docs/principles/{index.md => index.mdx} (88%) rename docusaurus/docs/principles/{object-design-rationale.md => object-design-rationale.mdx} (100%) rename docusaurus/docs/principles/{schema-design.md => schema-design.mdx} (100%) rename docusaurus/docs/principles/{stix-foundation.md => stix-foundation.mdx} (100%) rename docusaurus/docs/principles/{trade-offs.md => trade-offs.mdx} (100%) rename docusaurus/docs/principles/{versioning-philosophy.md => versioning-philosophy.mdx} (100%) rename docusaurus/docs/principles/{why-adm-exists.md => why-adm-exists.mdx} (100%) rename docusaurus/docs/principles/{why-zod.md => why-zod.mdx} (100%) rename docusaurus/docs/reference/api/{attack-data-model.md => attack-data-model.mdx} (100%) rename docusaurus/docs/reference/api/{data-sources.md => data-sources.mdx} (100%) rename docusaurus/docs/reference/api/{index.md => index.mdx} (100%) rename docusaurus/docs/reference/api/{utilities.md => utilities.mdx} (100%) rename docusaurus/docs/reference/{configuration.md => configuration.mdx} (100%) rename docusaurus/docs/reference/{errors.md => errors.mdx} (100%) rename docusaurus/docs/reference/{index.md => index.mdx} (100%) rename docusaurus/docs/reference/schemas/{stix-bundle.schema.md => stix-bundle.schema.mdx} (100%) diff --git a/docs/SPEC.md b/docs/SPEC.md index 62c73b00..42c0de5c 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,10 +1,10 @@ > [!IMPORTANT] > **Documentation Notice** -> +> > This document is **not the source of truth** for the ATT&CK specification. The authoritative source is the **ATT&CK Data Model (ADM) TypeScript library**. -> -> 📖 **Browse ATT&CK schemas:** https://mitre-attack.github.io/attack-data-model/ -> +> +> 📖 **Browse ATT&CK schemas:** +> > While maintained to the best of our ability, this documentation may drift from the ADM library. If you find discrepancies, please [open a GitHub Issue](https://github.com/mitre-attack/attack-data-model/issues). # The ATT&CK Specification @@ -61,8 +61,7 @@ There are three general ways that ATT&CK extends the STIX 2.1 specification: | `x_mitre_domains` | string[] | Identifies the domains the object is found in. See [domains](#domains) for more information.
Not found on objects of type `relationship`, `identity`, or `marking-definition`. | | `x_mitre_attack_spec_version`1 | string | The version of the ATT&CK specification used by the object. Consuming software can use this field to determine if the data format is supported. Current version is 3.3.0. | - -- New relationship types. Unlike custom object types and extended fields, custom relationship types are **not** prefixed with `x_mitre_`. You can find a full list of relationship types in the [Relationships](#Relationships) section, which also mentions whether the type is a default STIX type. +- New relationship types. Unlike custom object types and extended fields, custom relationship types are **not** prefixed with `x_mitre_`. You can find a full list of relationship types in the [Relationships](#relationships) section, which also mentions whether the type is a default STIX type. Please see also the STIX documentation on [customizing STIX](https://docs.oasis-open.org/cti/stix/v2.0/csprd01/part1-stix-core/stix-v2.0-csprd01-part1-stix-core.html#_Toc476227365). @@ -94,7 +93,7 @@ The `x_mitre_domains` (string array) field identifies the "domain" to which the | `mobile-attack` | Mobile | | `ics-attack` | ATT&CK for ICS | -Most objects in ATT&CK belong in a single technology domain, but an object _may_ be included in multiple domains. +Most objects in ATT&CK belong in a single technology domain, but an object _may_ be included in multiple domains. `x_mitre_domains` is supported on all ATT&CK object types except the following: @@ -128,6 +127,7 @@ ATT&CK IDs are human-readable identifiers commonly used for referencing objects | [Log Source](#log-sources) | `LSxxxx` | **Important limitations:** + - ATT&CK IDs are not guaranteed to be unique (see [Collisions with Technique ATT&CK IDs](#collisions-with-technique-attck-ids)) - Matrices within the same domain share identical ATT&CK IDs - Relationship objects do not have ATT&CK IDs @@ -214,6 +214,7 @@ Sub-techniques are specialized implementations of parent techniques, providing m Procedures describe specific instances of technique implementation by adversaries. Unlike other ATT&CK concepts, procedures are not represented by dedicated STIX objects. Instead, they are modeled as `uses` relationships where the `target_ref` points to a technique (`attack-pattern`). **Procedure relationships:** + - **Source objects:** Groups (`intrusion-set`) or software (`malware`/`tool`) - **Target objects:** Techniques (`attack-pattern`) - **Content:** Procedure details are captured in the relationship's `description` field @@ -251,6 +252,7 @@ Software represents tools and malicious code used by adversaries to accomplish t Data sources and data components define the telemetry and observational data that security teams can use to detect adversary techniques. This hierarchical model provides granular mapping between detection capabilities and techniques. **Structural relationships:** + - Data components are nested within data sources but maintain separate STIX objects - Each data component has exactly one parent data source - Data sources can contain multiple data components @@ -343,6 +345,7 @@ Detection strategies define high-level approaches for detecting specific adversa | `x_mitre_analytics` | string[] | Array of STIX IDs referencing `x-mitre-analytic` objects that implement this detection strategy. | **Key characteristics:** + - Each detection strategy has a one-to-one relationship with a specific ATT&CK technique - Detection strategies typically reference 1-3 analytics (one for each supported platform) - Uses soft relationships (STIX ID references) to analytics for flexibility @@ -352,33 +355,33 @@ Detection strategies define high-level approaches for detecting specific adversa The following diagrams illustrate how Detection Strategies connect to other ATT&CK objects through both formal STIX Relationship Objects (SROs) and soft relationships (STIX ID references): ``` - ┌────────────────────┐ - │ │ - │ <> │ ┌─────────────┐ - │ Detection │ │ │ - │ Strategy │ <> │ <> │ - │ ├─────────────► Technique │ - │┌──────────────────┐│ "detects" │ │ - ││x_mitre_analytics ││ └─────────────┘ - │└───────┬──────────┘│ - └────────┼───────────┘ - │ - │ "Soft" relationship - │ (STIX ID reference) - │ -┌─────────▼───────────┐ -│ │ -│ <> │ -│ Analytic │ -│ │ -│┌───────────────────┐│ -││x_mitre_log_sources││ -│└────────┬──────────┘│ -└─────────┼───────────┘ - │ - │ "Soft" relationship - │ (STIX ID reference) - │ + ┌────────────────────┐ + │ │ + │ <> │ ┌─────────────┐ + │ Detection │ │ │ + │ Strategy │ <> │ <> │ + │ ├─────────────► Technique │ + │┌──────────────────┐│ "detects" │ │ + ││x_mitre_analytics ││ └─────────────┘ + │└───────┬──────────┘│ + └────────┼───────────┘ + │ + │ "Soft" relationship + │ (STIX ID reference) + │ +┌─────────▼───────────┐ +│ │ +│ <> │ +│ Analytic │ +│ │ +│┌───────────────────┐│ +││x_mitre_log_sources││ +│└────────┬──────────┘│ +└─────────┼───────────┘ + │ + │ "Soft" relationship + │ (STIX ID reference) + │ ┌─────▼──────┐ ┌─────────────────┐ │ │ │ │ │ <> │ <> │ <> │ @@ -429,6 +432,7 @@ Log sources define immutable configurations for collecting security telemetry ac | `x_mitre_log_source_permutations` | `log_source_permutation[]` | Array of platform-specific log collection configurations. | **Key characteristics:** + - Each log source contains multiple permutations for different deployment scenarios - Permutations represent different ways to collect the same type of data across platforms - Connected to data components via `found-in` relationships @@ -443,6 +447,7 @@ The `log_source_permutation` object defines platform-specific collection configu | `channel` | string | Specific log channel or event type (e.g., "1" for Sysmon Process Creation) | **Example:** A single log source for 'Process Creation' might contain permutations for: + - Windows: (name: "sysmon", channel: "1") - Linux: (name: "auditd", channel: "SYSCALL") - macOS: (name: "unified_logs", channel: "process") @@ -475,4 +480,4 @@ Relationship objects frequently include `description` fields that provide contex ### Collections -See our [collections document](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md) for more information about the design and intention of collection objects. \ No newline at end of file +See our [collections document](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md) for more information about the design and intention of collection objects. diff --git a/docusaurus/docs/contributing/coding-style.md b/docusaurus/docs/contributing/coding-style.mdx similarity index 100% rename from docusaurus/docs/contributing/coding-style.md rename to docusaurus/docs/contributing/coding-style.mdx diff --git a/docusaurus/docs/contributing/dev-setup.md b/docusaurus/docs/contributing/dev-setup.mdx similarity index 90% rename from docusaurus/docs/contributing/dev-setup.md rename to docusaurus/docs/contributing/dev-setup.mdx index f27424fc..0b035eb0 100644 --- a/docusaurus/docs/contributing/dev-setup.md +++ b/docusaurus/docs/contributing/dev-setup.mdx @@ -30,6 +30,6 @@ npm run test # vitest tests | npm run test | Run tests | You are now ready to hack on the library. -Continue with [Coding Style & Linting](coding-style.md) for the mandatory style rules. +Continue with [Coding Style & Linting](./coding-style) for the mandatory style rules. --- diff --git a/docusaurus/docs/contributing/docs.md b/docusaurus/docs/contributing/docs.mdx similarity index 100% rename from docusaurus/docs/contributing/docs.md rename to docusaurus/docs/contributing/docs.mdx diff --git a/docusaurus/docs/contributing/index.md b/docusaurus/docs/contributing/index.mdx similarity index 100% rename from docusaurus/docs/contributing/index.md rename to docusaurus/docs/contributing/index.mdx diff --git a/docusaurus/docs/contributing/tests.md b/docusaurus/docs/contributing/tests.mdx similarity index 100% rename from docusaurus/docs/contributing/tests.md rename to docusaurus/docs/contributing/tests.mdx diff --git a/docusaurus/docs/how-to-guides/error-handling.md b/docusaurus/docs/how-to-guides/error-handling.mdx similarity index 100% rename from docusaurus/docs/how-to-guides/error-handling.md rename to docusaurus/docs/how-to-guides/error-handling.mdx diff --git a/docusaurus/docs/how-to-guides/index.md b/docusaurus/docs/how-to-guides/index.mdx similarity index 100% rename from docusaurus/docs/how-to-guides/index.md rename to docusaurus/docs/how-to-guides/index.mdx diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.md b/docusaurus/docs/how-to-guides/manage-data-sources.mdx similarity index 100% rename from docusaurus/docs/how-to-guides/manage-data-sources.md rename to docusaurus/docs/how-to-guides/manage-data-sources.mdx diff --git a/docusaurus/docs/how-to-guides/performance.md b/docusaurus/docs/how-to-guides/performance.mdx similarity index 100% rename from docusaurus/docs/how-to-guides/performance.md rename to docusaurus/docs/how-to-guides/performance.mdx diff --git a/docusaurus/docs/how-to-guides/validate-bundles.md b/docusaurus/docs/how-to-guides/validate-bundles.mdx similarity index 100% rename from docusaurus/docs/how-to-guides/validate-bundles.md rename to docusaurus/docs/how-to-guides/validate-bundles.mdx diff --git a/docusaurus/docs/index.md b/docusaurus/docs/index.mdx similarity index 100% rename from docusaurus/docs/index.md rename to docusaurus/docs/index.mdx diff --git a/docusaurus/docs/overview.md b/docusaurus/docs/overview.mdx similarity index 100% rename from docusaurus/docs/overview.md rename to docusaurus/docs/overview.mdx diff --git a/docusaurus/docs/principles/attack-specification-overview.md b/docusaurus/docs/principles/attack-specification-overview.mdx similarity index 100% rename from docusaurus/docs/principles/attack-specification-overview.md rename to docusaurus/docs/principles/attack-specification-overview.mdx diff --git a/docusaurus/docs/principles/compatibility.md b/docusaurus/docs/principles/compatibility.mdx similarity index 100% rename from docusaurus/docs/principles/compatibility.md rename to docusaurus/docs/principles/compatibility.mdx diff --git a/docusaurus/docs/principles/index.md b/docusaurus/docs/principles/index.mdx similarity index 88% rename from docusaurus/docs/principles/index.md rename to docusaurus/docs/principles/index.mdx index 6cd46753..e445206a 100644 --- a/docusaurus/docs/principles/index.md +++ b/docusaurus/docs/principles/index.mdx @@ -6,7 +6,8 @@ import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; **Understanding-oriented content about design decisions and architecture** -This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. +This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. +These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. ## Dive Deeper diff --git a/docusaurus/docs/principles/object-design-rationale.md b/docusaurus/docs/principles/object-design-rationale.mdx similarity index 100% rename from docusaurus/docs/principles/object-design-rationale.md rename to docusaurus/docs/principles/object-design-rationale.mdx diff --git a/docusaurus/docs/principles/schema-design.md b/docusaurus/docs/principles/schema-design.mdx similarity index 100% rename from docusaurus/docs/principles/schema-design.md rename to docusaurus/docs/principles/schema-design.mdx diff --git a/docusaurus/docs/principles/stix-foundation.md b/docusaurus/docs/principles/stix-foundation.mdx similarity index 100% rename from docusaurus/docs/principles/stix-foundation.md rename to docusaurus/docs/principles/stix-foundation.mdx diff --git a/docusaurus/docs/principles/trade-offs.md b/docusaurus/docs/principles/trade-offs.mdx similarity index 100% rename from docusaurus/docs/principles/trade-offs.md rename to docusaurus/docs/principles/trade-offs.mdx diff --git a/docusaurus/docs/principles/versioning-philosophy.md b/docusaurus/docs/principles/versioning-philosophy.mdx similarity index 100% rename from docusaurus/docs/principles/versioning-philosophy.md rename to docusaurus/docs/principles/versioning-philosophy.mdx diff --git a/docusaurus/docs/principles/why-adm-exists.md b/docusaurus/docs/principles/why-adm-exists.mdx similarity index 100% rename from docusaurus/docs/principles/why-adm-exists.md rename to docusaurus/docs/principles/why-adm-exists.mdx diff --git a/docusaurus/docs/principles/why-zod.md b/docusaurus/docs/principles/why-zod.mdx similarity index 100% rename from docusaurus/docs/principles/why-zod.md rename to docusaurus/docs/principles/why-zod.mdx diff --git a/docusaurus/docs/reference/api/attack-data-model.md b/docusaurus/docs/reference/api/attack-data-model.mdx similarity index 100% rename from docusaurus/docs/reference/api/attack-data-model.md rename to docusaurus/docs/reference/api/attack-data-model.mdx diff --git a/docusaurus/docs/reference/api/data-sources.md b/docusaurus/docs/reference/api/data-sources.mdx similarity index 100% rename from docusaurus/docs/reference/api/data-sources.md rename to docusaurus/docs/reference/api/data-sources.mdx diff --git a/docusaurus/docs/reference/api/index.md b/docusaurus/docs/reference/api/index.mdx similarity index 100% rename from docusaurus/docs/reference/api/index.md rename to docusaurus/docs/reference/api/index.mdx diff --git a/docusaurus/docs/reference/api/utilities.md b/docusaurus/docs/reference/api/utilities.mdx similarity index 100% rename from docusaurus/docs/reference/api/utilities.md rename to docusaurus/docs/reference/api/utilities.mdx diff --git a/docusaurus/docs/reference/configuration.md b/docusaurus/docs/reference/configuration.mdx similarity index 100% rename from docusaurus/docs/reference/configuration.md rename to docusaurus/docs/reference/configuration.mdx diff --git a/docusaurus/docs/reference/errors.md b/docusaurus/docs/reference/errors.mdx similarity index 100% rename from docusaurus/docs/reference/errors.md rename to docusaurus/docs/reference/errors.mdx diff --git a/docusaurus/docs/reference/index.md b/docusaurus/docs/reference/index.mdx similarity index 100% rename from docusaurus/docs/reference/index.md rename to docusaurus/docs/reference/index.mdx diff --git a/docusaurus/docs/reference/schemas/stix-bundle.schema.md b/docusaurus/docs/reference/schemas/stix-bundle.schema.mdx similarity index 100% rename from docusaurus/docs/reference/schemas/stix-bundle.schema.md rename to docusaurus/docs/reference/schemas/stix-bundle.schema.mdx diff --git a/docusaurus/package-lock.json b/docusaurus/package-lock.json index 47c6c92a..132db694 100644 --- a/docusaurus/package-lock.json +++ b/docusaurus/package-lock.json @@ -20,6 +20,8 @@ "@docusaurus/module-type-aliases": "3.8.1", "@docusaurus/tsconfig": "3.8.1", "@docusaurus/types": "3.8.1", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "typescript": "~5.8.3" }, "engines": { @@ -3826,6 +3828,197 @@ "node": ">=18.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3841,6 +4034,124 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -4014,12 +4325,334 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/config": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.3.4.tgz", + "integrity": "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/config/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@npmcli/config/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 8" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, "node_modules/@pnpm/config.env-replace": { @@ -4416,6 +5049,16 @@ "@types/node": "*" } }, + "node_modules/@types/concat-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-2.0.3.tgz", + "integrity": "sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -4543,6 +5186,13 @@ "@types/node": "*" } }, + "node_modules/@types/is-empty": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/is-empty/-/is-empty-1.2.3.tgz", + "integrity": "sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4738,6 +5388,13 @@ "@types/node": "*" } }, + "node_modules/@types/supports-color": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -4932,6 +5589,16 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -4976,9 +5643,9 @@ } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6111,6 +6778,22 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -6350,9 +7033,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6880,6 +7563,14 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -7017,6 +7708,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7242,6 +7943,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -7303,54 +8011,372 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-mdx/-/eslint-mdx-3.6.2.tgz", + "integrity": "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "espree": "^9.6.1 || ^10.4.0", + "estree-util-visit": "^2.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "unified-engine": "^11.2.2", + "unist-util-visit": "^5.0.0", + "uvu": "^0.5.6", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0", + "remark-lint-file-extension": "*" + }, + "peerDependenciesMeta": { + "remark-lint-file-extension": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mdx/-/eslint-plugin-mdx-3.6.2.tgz", + "integrity": "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-mdx": "^3.6.2", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { + "node_modules/eslint/node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -7358,17 +8384,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7384,6 +8415,31 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -7731,6 +8787,14 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -7817,6 +8881,20 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -7972,6 +9050,29 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -7992,6 +9093,36 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data-encoder": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", @@ -8616,6 +9747,26 @@ "react-is": "^16.7.0" } }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -8995,6 +10146,17 @@ "node": ">=8" } }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9166,6 +10328,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -9363,6 +10532,22 @@ "node": ">=0.10.0" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -9480,6 +10665,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9565,6 +10758,21 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -9583,6 +10791,21 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/load-plugin": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.3.tgz", + "integrity": "sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@npmcli/config": "^8.0.0", + "import-meta-resolve": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -9639,6 +10862,14 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -12088,6 +13319,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -12134,6 +13385,14 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -12189,6 +13448,37 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -12215,8 +13505,63 @@ "engines": { "node": ">=14.16" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-run-path": { @@ -12445,6 +13790,25 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -12567,6 +13931,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -12733,6 +14104,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", @@ -14240,6 +15635,17 @@ "postcss": "^8.4.31" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -14281,12 +15687,53 @@ "node": ">=6" } }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -14635,6 +16082,30 @@ "react": ">=15" } }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -15248,6 +16719,19 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -15852,6 +17336,42 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -15941,6 +17461,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -16008,6 +17551,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -16136,6 +17693,22 @@ "node": ">= 10" } }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -16364,6 +17937,20 @@ "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -16410,6 +17997,13 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -16439,72 +18033,216 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-11.2.2.tgz", + "integrity": "sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/concat-stream": "^2.0.0", + "@types/debug": "^4.0.0", + "@types/is-empty": "^1.0.0", + "@types/node": "^22.0.0", + "@types/unist": "^3.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.0.0", + "extend": "^3.0.0", + "glob": "^10.0.0", + "ignore": "^6.0.0", + "is-empty": "^1.0.0", + "is-plain-obj": "^4.0.0", + "load-plugin": "^6.0.0", + "parse-json": "^7.0.0", + "trough": "^2.0.0", + "unist-util-inspect": "^8.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-reporter": "^8.0.0", + "vfile-statistics": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/unified-engine/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unified-engine/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 4" } }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "node_modules/unified-engine/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/unified-engine/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "license": "MIT", + "node_modules/unified-engine/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "node_modules/unified-engine/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, "engines": { - "node": ">=4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" + "node_modules/unified-engine/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/unique-string": { @@ -16522,6 +18260,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-inspect": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-8.1.0.tgz", + "integrity": "sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -16870,6 +18622,56 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -16927,6 +18729,131 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-reporter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-8.1.1.tgz", + "integrity": "sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/supports-color": "^8.0.0", + "string-width": "^6.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-sort": "^4.0.0", + "vfile-statistics": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/vfile-sort": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-4.0.0.tgz", + "integrity": "sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-3.0.0.tgz", + "integrity": "sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -17397,6 +19324,17 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "license": "MIT" }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -17414,6 +19352,47 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -17522,6 +19501,19 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/docusaurus/package.json b/docusaurus/package.json index f3a9f64e..c9fb7884 100644 --- a/docusaurus/package.json +++ b/docusaurus/package.json @@ -24,6 +24,8 @@ "@docusaurus/module-type-aliases": "3.8.1", "@docusaurus/tsconfig": "3.8.1", "@docusaurus/types": "3.8.1", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "typescript": "~5.8.3" }, "browserslist": { diff --git a/eslint.config.js b/eslint.config.js index feb2ba41..1a265844 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,6 +2,7 @@ import pluginJs from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; import tseslint from 'typescript-eslint'; +import * as mdx from 'eslint-plugin-mdx'; export default [ { @@ -12,4 +13,19 @@ export default [ pluginJs.configs.recommended, ...tseslint.configs.recommended, eslintPluginPrettierRecommended, + + // MDX file support (in the documentation) + { + files: ['**/*.mdx'], + processor: mdx.processors.mdx, + }, + { + files: ['**/*.{md,mdx}'], + plugins: { + mdx, + }, + rules: { + 'mdx/no-unused-expressions': 'error', + }, + }, ]; diff --git a/generate-docs.sh b/generate-docs.sh index 896ca2fe..b4386b54 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -3,7 +3,7 @@ WORKDIR="." SCHEMA_DIR="$WORKDIR/src/schemas" OUTPUT_DIR="docusaurus/docs/reference/schemas" -OVERVIEW="$OUTPUT_DIR/index.md" +OVERVIEW="$OUTPUT_DIR/index.mdx" mkdir -p $OUTPUT_DIR @@ -42,14 +42,14 @@ find $SCHEMA_DIR -name "*.schema.ts" | while read schemaFile; do title=$(echo "$title" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1') # set output file path - outputFile="$OUTPUT_DIR/${relativePath/.ts/.md}" + outputFile="$OUTPUT_DIR/${relativePath/.ts/.mdx}" # convert zod schemas to md # pass the parent tsconfig to resolve path aliases npx zod2md --entry $schemaFile --title "$title Schema" --output "$outputFile" --tsconfig "$WORKDIR/tsconfig.json" # add schema to overview table - schemaLink="${relativePath/.ts/.md}" + schemaLink="${relativePath/.ts/.mdx}" stixType=$(dirname "$relativePath" | cut -d '/' -f 1 | tr '[:lower:]' '[:upper:]') echo "| $title | $stixType | [Schema](./$schemaLink) |" >> $OVERVIEW diff --git a/package-lock.json b/package-lock.json index 092d746a..cc00e69d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-prettier": "^5.4.1", "globals": "^15.11.0", "husky": "^9.1.7", @@ -1255,6 +1257,226 @@ "node": ">= 8" } }, + "node_modules/@npmcli/config": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.3.4.tgz", + "integrity": "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/config/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -2355,6 +2577,16 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/concat-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-2.0.3.tgz", + "integrity": "sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -2365,6 +2597,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -2379,6 +2621,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/is-empty": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/is-empty/-/is-empty-1.2.3.tgz", + "integrity": "sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2386,6 +2655,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", @@ -2403,6 +2689,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/supports-color": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -2816,6 +3116,16 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3027,6 +3337,17 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3072,6 +3393,13 @@ "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -3121,6 +3449,17 @@ "node": ">=6" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -3161,36 +3500,96 @@ "node": ">=10" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 16" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true, "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, "funding": { - "url": "https://paulmillr.com/funding/" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "license": "MIT", "engines": { @@ -3436,6 +3835,37 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/confbox": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", @@ -3705,6 +4135,20 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3741,6 +4185,30 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -4003,6 +4471,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4203,6 +4678,73 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-mdx/-/eslint-mdx-3.6.2.tgz", + "integrity": "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "espree": "^9.6.1 || ^10.4.0", + "estree-util-visit": "^2.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "unified-engine": "^11.2.2", + "unist-util-visit": "^5.0.0", + "uvu": "^0.5.6", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0", + "remark-lint-file-extension": "*" + }, + "peerDependenciesMeta": { + "remark-lint-file-extension": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mdx/-/eslint-plugin-mdx-3.6.2.tgz", + "integrity": "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-mdx": "^3.6.2", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", @@ -4463,6 +5005,32 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -4517,6 +5085,13 @@ "node": ">=12.0.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-content-type-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", @@ -5443,6 +6018,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5450,6 +6051,24 @@ "dev": true, "license": "MIT" }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5483,6 +6102,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5802,6 +6432,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5866,6 +6506,21 @@ "node": ">=4" } }, + "node_modules/load-plugin": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.3.tgz", + "integrity": "sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@npmcli/config": "^8.0.0", + "import-meta-resolve": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", @@ -6004,6 +6659,17 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loupe": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", @@ -6107,44 +6773,825 @@ "node": ">= 0.4" } }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=16.10" + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6244,6 +7691,16 @@ "ufo": "^1.5.4" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6319,6 +7776,22 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -6521,6 +7994,74 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -9224,6 +10765,33 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9600,6 +11168,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9607,6 +11185,27 @@ "dev": true, "license": "MIT" }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -9684,6 +11283,30 @@ "node": ">=0.10.0" } }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -9783,6 +11406,54 @@ "node": ">=14" } }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9813,6 +11484,16 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9888,6 +11569,19 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -10520,6 +12214,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11007,6 +12716,17 @@ "tree-kill": "cli.js" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -11170,6 +12890,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -11258,6 +12985,140 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-11.2.2.tgz", + "integrity": "sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/concat-stream": "^2.0.0", + "@types/debug": "^4.0.0", + "@types/is-empty": "^1.0.0", + "@types/node": "^22.0.0", + "@types/unist": "^3.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.0.0", + "extend": "^3.0.0", + "glob": "^10.0.0", + "ignore": "^6.0.0", + "is-empty": "^1.0.0", + "is-plain-obj": "^4.0.0", + "load-plugin": "^6.0.0", + "parse-json": "^7.0.0", + "trough": "^2.0.0", + "unist-util-inspect": "^8.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-reporter": "^8.0.0", + "vfile-statistics": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine/node_modules/@types/node": { + "version": "22.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", + "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/unified-engine/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/unified-engine/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unified-engine/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/unified-engine/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified-engine/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified-engine/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -11274,6 +13135,93 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-inspect": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-8.1.0.tgz", + "integrity": "sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -11331,6 +13279,35 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -11349,6 +13326,151 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-8.1.1.tgz", + "integrity": "sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/supports-color": "^8.0.0", + "string-width": "^6.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-sort": "^4.0.0", + "vfile-statistics": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/vfile-sort": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-4.0.0.tgz", + "integrity": "sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-3.0.0.tgz", + "integrity": "sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", @@ -11568,6 +13690,13 @@ "dev": true, "license": "MIT" }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -11751,6 +13880,19 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11863,6 +14005,17 @@ "engines": { "node": ">=18" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 175a6f12..51b26bd2 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,8 @@ "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-prettier": "^5.4.1", "globals": "^15.11.0", "husky": "^9.1.7", From 001728f7320330dcde9261cd494590e299f5ee9e Mon Sep 17 00:00:00 2001 From: adpare Date: Sun, 24 Aug 2025 23:42:49 -0400 Subject: [PATCH 32/39] docs: complete tutorials documentation --- .../docs/tutorials/multi-domain-analysis.md | 29 +- docusaurus/docs/tutorials/relationships.md | 145 ++++++--- docusaurus/docs/tutorials/your-first-query.md | 112 ++++--- examples/first-query.ts | 41 ++- examples/multi-domain.ts | 171 ++++++++++ examples/relationship-explorer.ts | 295 ++++++++++++++++++ 6 files changed, 673 insertions(+), 120 deletions(-) create mode 100644 examples/multi-domain.ts create mode 100644 examples/relationship-explorer.ts diff --git a/docusaurus/docs/tutorials/multi-domain-analysis.md b/docusaurus/docs/tutorials/multi-domain-analysis.md index efe8e06c..31519a77 100644 --- a/docusaurus/docs/tutorials/multi-domain-analysis.md +++ b/docusaurus/docs/tutorials/multi-domain-analysis.md @@ -39,13 +39,14 @@ npm install -D typescript tsx @types/node Create a file named `multi-domain.ts` and add the following code: ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; async function loadAllDomains() { - console.log('🌐 Loading all ATT&CK domains...\n'); + console.log('Loading all ATT&CK domains...\n'); // Define all three domains - const domains = [ + type DomainName = "enterprise-attack" | "mobile-attack" | "ics-attack"; + const domains: { name: DomainName; label: string }[] = [ { name: 'enterprise-attack', label: 'Enterprise' }, { name: 'mobile-attack', label: 'Mobile' }, { name: 'ics-attack', label: 'ICS' } @@ -55,12 +56,12 @@ async function loadAllDomains() { for (const domain of domains) { try { - console.log(`📡 Loading ${domain.label} domain...`); + console.log(`Loading ${domain.label} domain...`); - const dataSource = new DataSource({ + const dataSource = new DataSourceRegistration({ source: 'attack', domain: domain.name, - version: '15.1', + version: '17.1', parsingMode: 'relaxed' }); @@ -68,11 +69,11 @@ async function loadAllDomains() { if (uuid) { dataModels[domain.name] = loadDataModel(uuid); const techniqueCount = dataModels[domain.name].techniques.length; - console.log(`✅ ${domain.label}: ${techniqueCount} techniques loaded\n`); + console.log(`${domain.label}: ${techniqueCount} techniques loaded\n`); } } catch (error) { - console.error(`❌ Failed to load ${domain.label} domain:`, error); + console.error(`Failed to load ${domain.label} domain:`, error); } } @@ -86,7 +87,7 @@ Add this function to analyze differences between domains: ```typescript function analyzeDomainStatistics(dataModels: { [key: string]: any }) { - console.log('📊 Domain Comparison:\n'); + console.log('Domain Comparison:\n'); const stats: { [key: string]: any } = {}; @@ -126,7 +127,7 @@ Add this function to identify tactics that appear in multiple domains: ```typescript function findCommonTactics(dataModels: { [key: string]: any }) { - console.log('🎯 Tactic Analysis:\n'); + console.log('Tactic Analysis:\n'); const tacticsByDomain: { [key: string]: Set } = {}; @@ -175,7 +176,7 @@ Add this function to find techniques that may be related across domains: ```typescript function analyzeCrossDomainTechniques(dataModels: { [key: string]: any }) { - console.log('🔍 Cross-Domain Technique Analysis:\n'); + console.log('Cross-Domain Technique Analysis:\n'); // Look for techniques with similar names across domains const enterpriseTechniques = dataModels['enterprise-attack']?.techniques || []; @@ -215,7 +216,7 @@ async function performMultiDomainAnalysis() { const dataModels = await loadAllDomains(); if (Object.keys(dataModels).length === 0) { - console.error('❌ No domains loaded successfully'); + console.error('No domains loaded successfully'); return; } @@ -224,10 +225,10 @@ async function performMultiDomainAnalysis() { findCommonTactics(dataModels); analyzeCrossDomainTechniques(dataModels); - console.log('✅ Multi-domain analysis complete!'); + console.log('Multi-domain analysis complete!'); } catch (error) { - console.error('❌ Analysis failed:', error); + console.error('Analysis failed:', error); } } diff --git a/docusaurus/docs/tutorials/relationships.md b/docusaurus/docs/tutorials/relationships.md index 6ef73148..f9981aae 100644 --- a/docusaurus/docs/tutorials/relationships.md +++ b/docusaurus/docs/tutorials/relationships.md @@ -41,18 +41,18 @@ npm install -D typescript tsx @types/node Create `relationship-explorer.ts`: ```typescript -import { registerDataSource, loadDataModel, DataSource, AttackDataModel } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration, AttackDataModel } from '@mitre-attack/attack-data-model'; class RelationshipExplorer { private attackDataModel: AttackDataModel; async initialize(): Promise { - console.log('🔗 Initializing ATT&CK Relationship Explorer...\n'); + console.log('Initializing ATT&CK Relationship Explorer...\n'); - const dataSource = new DataSource({ + const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', - version: '15.1', + version: '17.1', parsingMode: 'relaxed' }); @@ -60,9 +60,20 @@ class RelationshipExplorer { if (!uuid) throw new Error('Failed to register data source'); this.attackDataModel = loadDataModel(uuid); - console.log('✅ Data loaded successfully!\n'); + console.log('Data loaded successfully!\n'); } + // Returns an array of tactic objects for a given technique + getTechniqueTactics(technique: any): any[] { + const killChainPhases = technique.kill_chain_phases || []; + const tacticShortnames = killChainPhases.map(phase => phase.phase_name); + + const tactics = this.attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + return tactics; + } + // We'll add exploration methods here... } @@ -83,28 +94,28 @@ Add this method to understand how techniques map to tactical goals: ```typescript exploreTechniqueTactics(): void { - console.log('🎯 TECHNIQUE-TACTIC RELATIONSHIPS\n'); + console.log('TECHNIQUE-TACTIC RELATIONSHIPS\n'); // Find a specific technique const technique = this.attackDataModel.techniques.find(t => t.external_references[0].external_id === 'T1055' - ); - + ) as TechniqueImpl | undefined; + if (!technique) return; - console.log(`📋 Technique: ${technique.name} (${technique.external_references[0].external_id})`); - console.log(`📝 Description: ${technique.description.substring(0, 100)}...\n`); + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 100)}...\n`); // Get associated tactics - const tactics = technique.getTactics(); - console.log(`🎯 This technique is used for ${tactics.length} tactical goal(s):`); + const tactics = this.getTechniqueTactics(technique); + console.log(`This technique is used for ${tactics.length} tactical goal(s):`); tactics.forEach((tactic, index) => { console.log(`${index + 1}. ${tactic.name}`); console.log(` Purpose: ${tactic.description.substring(0, 80)}...\n`); }); - console.log('💡 This shows how one technique can serve multiple adversary goals!\n'); + console.log('This shows how one technique can serve multiple adversary goals!\n'); } ``` @@ -121,7 +132,7 @@ Add this method to explore how groups use techniques: ```typescript exploreGroupTechniques(): void { - console.log('👥 GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); + console.log('GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); // Find APT1 group const apt1 = this.attackDataModel.groups.find(g => @@ -130,12 +141,12 @@ Add this method to explore how groups use techniques: if (!apt1) return; - console.log(`🏴 Group: ${apt1.name} (${apt1.external_references[0].external_id})`); - console.log(`📝 Description: ${apt1.description.substring(0, 120)}...\n`); + console.log(`Group: ${apt1.name} (${apt1.external_references[0].external_id})`); + console.log(`Description: ${apt1.description.substring(0, 120)}...\n`); // Get techniques used by this group const techniques = apt1.getTechniques(); - console.log(`⚔️ This group uses ${techniques.length} different techniques:`); + console.log(`This group uses ${techniques.length} different techniques:`); // Show first 5 techniques techniques.slice(0, 5).forEach((technique, index) => { @@ -164,7 +175,7 @@ Add this method to understand software-technique relationships: ```typescript exploreSoftwareUsage(): void { - console.log('💻 SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); + console.log('SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); // Find Mimikatz (a well-known tool) const mimikatz = this.attackDataModel.tools.find(tool => @@ -173,27 +184,38 @@ Add this method to understand software-technique relationships: if (!mimikatz) return; - console.log(`🔧 Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); - console.log(`📝 Description: ${mimikatz.description.substring(0, 120)}...\n`); + console.log(`Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); + console.log(`Description: ${mimikatz.description.substring(0, 120)}...\n`); // Get techniques used by this software const techniques = mimikatz.getTechniques(); - console.log(`⚡ This tool implements ${techniques.length} techniques:`); + console.log(`This tool implements ${techniques.length} techniques:`); techniques.forEach((technique, index) => { console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); // Show which tactics this technique supports - const tactics = technique.getTactics(); + const tactics = this.getTechniqueTactics(technique); + console.log(` Supports tactics: ${tactics.map(t => t.name).join(', ')}\n`); }); + const relationships = this.attackDataModel.relationships; - // Find groups that use this software - const groupsUsingMimikatz = this.attackDataModel.groups.filter(group => - group.getAssociatedSoftware().some(software => software.id === mimikatz.id) + const mimikatzId = mimikatz.id; + + // Find all "uses" relationships where target is Mimikatz + const groupUsesMimikatz = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.target_ref === mimikatzId && + rel.source_ref.startsWith("intrusion-set--") // group id prefix ); - console.log(`👥 This tool is used by ${groupsUsingMimikatz.length} groups:`); + // Get group objects + const groupsUsingMimikatz = groupUsesMimikatz.map(rel => + this.attackDataModel.groups.find(group => group.id === rel.source_ref) + ).filter(Boolean); // Remove undefined if any + + console.log(`This tool is used by ${groupsUsingMimikatz.length} groups:`); groupsUsingMimikatz.slice(0, 3).forEach((group, index) => { console.log(`${index + 1}. ${group.name} (${group.external_references[0].external_id})`); }); @@ -207,22 +229,23 @@ Add this method to explore sub-technique hierarchies: ```typescript exploreSubtechniqueRelationships(): void { - console.log('🌳 PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); + console.log('PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); // Find a parent technique with sub-techniques const parentTechnique = this.attackDataModel.techniques.find(t => t.external_references[0].external_id === 'T1003' && !t.x_mitre_is_subtechnique - ); + ) as TechniqueImpl | undefined; if (!parentTechnique) return; - console.log(`👨‍👧‍👦 Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); - console.log(`📝 Description: ${parentTechnique.description.substring(0, 120)}...\n`); + console.log(`Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); + console.log(`Description: ${parentTechnique.description.substring(0, 120)}...\n`); // Get sub-techniques - const subTechniques = parentTechnique.getSubtechniques(); - console.log(`🌿 This technique has ${subTechniques.length} sub-techniques:`); + + const subTechniques = parentTechnique.getSubTechniques(); + console.log(`This technique has ${subTechniques.length} sub-techniques:`); subTechniques.forEach((subTech, index) => { console.log(`${index + 1}. ${subTech.name} (${subTech.external_references[0].external_id})`); @@ -234,9 +257,9 @@ Add this method to explore sub-technique hierarchies: const firstSubTech = subTechniques[0]; const parentFromChild = firstSubTech.getParentTechnique(); - console.log(`🔄 Navigation verification:`); + console.log(`Navigation verification:`); console.log(`Sub-technique "${firstSubTech.name}" → Parent: "${parentFromChild?.name}"`); - console.log(`✅ Bidirectional navigation works!\n`); + console.log(`Bidirectional navigation works!\n`); } } ``` @@ -247,7 +270,7 @@ Add this method to find defensive measures: ```typescript exploreMitigationRelationships(): void { - console.log('🛡️ MITIGATION-TECHNIQUE RELATIONSHIPS\n'); + console.log('MITIGATION-TECHNIQUE RELATIONSHIPS\n'); // Find a technique const technique = this.attackDataModel.techniques.find(t => @@ -256,12 +279,12 @@ Add this method to find defensive measures: if (!technique) return; - console.log(`⚔️ Technique: ${technique.name} (${technique.external_references[0].external_id})`); - console.log(`📝 Description: ${technique.description.substring(0, 120)}...\n`); + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 120)}...\n`); // Get mitigations for this technique const mitigations = technique.getMitigations(); - console.log(`🛡️ This technique can be mitigated by ${mitigations.length} measures:`); + console.log(`This technique can be mitigated by ${mitigations.length} measures:`); mitigations.forEach((mitigation, index) => { console.log(`${index + 1}. ${mitigation.name} (${mitigation.external_references[0].external_id})`); @@ -286,7 +309,7 @@ Add this method to trace complex relationship chains: ```typescript exploreTransitiveRelationships(): void { - console.log('🔄 TRANSITIVE RELATIONSHIPS (Group → Software → Techniques)\n'); + console.log('TRANSITIVE RELATIONSHIPS (Group → Software → Techniques)\n'); // Find a group const group = this.attackDataModel.groups.find(g => @@ -295,25 +318,45 @@ Add this method to trace complex relationship chains: if (!group) return; - console.log(`👥 Group: ${group.name} (${group.external_references[0].external_id})`); - console.log(`📝 Description: ${group.description.substring(0, 120)}...\n`); + console.log(`Group: ${group.name} (${group.external_references[0].external_id})`); + console.log(`Description: ${group.description.substring(0, 120)}...\n`); + + const relationships = this.attackDataModel.relationships; // Get software used by the group - const software = group.getAssociatedSoftware(); - console.log(`💻 This group uses ${software.length} software tools:`); + const softwareUsedByGroup = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.source_ref === group.id && + ( + rel.target_ref.startsWith("malware--") || + rel.target_ref.startsWith("tool--") + ) + ); + + // Get software objects + const allSoftware = [ + ...this.attackDataModel.malware, + ...this.attackDataModel.tools + ]; + const software = softwareUsedByGroup.map(rel => + allSoftware.find(soft => soft.id === rel.target_ref) + ).filter(Boolean); + + console.log(`This group uses ${software.length} software tools:`); - software.slice(0, 3).forEach((tool, index) => { - console.log(`\n${index + 1}. ${tool.name} (${tool.external_references[0].external_id})`); + software.slice(0, 3).forEach((soft, index) => { + console.log(`\n${index + 1}. ${soft.name} (${soft.external_references[0].external_id})`); // Get techniques used by this software - const techniques = tool.getTechniques(); + const techniques = soft.getTechniques(); console.log(` → Implements ${techniques.length} techniques:`); techniques.slice(0, 2).forEach((technique, techIndex) => { console.log(` ${techIndex + 1}. ${technique.name} (${technique.external_references[0].external_id})`); // Show tactics supported - const tactics = technique.getTactics(); + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); console.log(` Tactics: ${tactics.map(t => t.name).join(', ')}`); }); @@ -322,7 +365,7 @@ Add this method to trace complex relationship chains: } }); - console.log(`\n💡 This shows the relationship chain: Group → Software → Techniques → Tactics\n`); + console.log(`\nThis shows the relationship chain: Group → Software → Techniques → Tactics\n`); } ``` @@ -343,8 +386,8 @@ async function main() { explorer.exploreMitigationRelationships(); explorer.exploreTransitiveRelationships(); - console.log('🎉 Relationship exploration complete!\n'); - console.log('💡 Key takeaways:'); + console.log('Relationship exploration complete!\n'); + console.log('Key takeaways:'); console.log(' - ATT&CK objects are richly interconnected'); console.log(' - Relationships carry descriptive context'); console.log(' - Navigation methods simplify complex queries'); @@ -409,7 +452,7 @@ const unmitigatedTechniques = attackDataModel.techniques.filter(tech => // Find software used across multiple tactics const multiTacticSoftware = attackDataModel.tools.filter(tool => - new Set(tool.getTechniques().flatMap(tech => tech.getTactics())).size > 3 + new Set(tool.getTechniques().flatMap(tech => this.getTechniqueTactics(tech))).size > 3 ); ``` diff --git a/docusaurus/docs/tutorials/your-first-query.md b/docusaurus/docs/tutorials/your-first-query.md index 8d820a61..b4778d63 100644 --- a/docusaurus/docs/tutorials/your-first-query.md +++ b/docusaurus/docs/tutorials/your-first-query.md @@ -47,43 +47,47 @@ You should see the packages being installed. The installation may take a minute Create a file named `first-query.ts` and add the following code: ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { + registerDataSource, + loadDataModel, + DataSourceRegistration, +} from '@mitre-attack/attack-data-model'; async function exploreAttackData() { - console.log('🎯 Loading ATT&CK Enterprise data...\n'); - - // Step 1: Create a data source - const dataSource = new DataSource({ - source: 'attack', // Load from official ATT&CK repository - domain: 'enterprise-attack', // Focus on Enterprise domain - version: '15.1', // Use specific version for consistency - parsingMode: 'relaxed' // Continue even if some data has minor issues - }); - - try { - // Step 2: Register the data source - const uuid = await registerDataSource(dataSource); - - if (uuid) { - console.log('✅ Data source registered successfully!\n'); - - // Step 3: Load the data model - const attackDataModel = loadDataModel(uuid); - console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); - - // Step 4: Explore the first few techniques - console.log('🔍 First 5 techniques:'); - attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { - console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); - }); - - } else { - console.error('❌ Failed to register data source'); - } - - } catch (error) { - console.error('❌ Error:', error); + console.log('Loading ATT&CK Enterprise data...\n'); + + // Step 1: Create a data source + const dataSource = new DataSourceRegistration({ + source: 'attack', // Load from official ATT&CK repository + domain: 'enterprise-attack', // Focus on Enterprise domain + version: '17.1', // Use specific version for consistency + parsingMode: 'relaxed', // Continue even if some data has minor issues + }); + + try { + // Step 2: Register the data source + const uuid = await registerDataSource(dataSource); + + if (uuid) { + console.log('Data source registered successfully!\n'); + + // Step 3: Load the data model + const attackDataModel = loadDataModel(uuid); + console.log(`Loaded ${attackDataModel.techniques.length} techniques\n`); + + // Step 4: Explore the first few techniques + console.log('First 5 techniques:'); + attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { + console.log( + `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`, + ); + }); + } else { + console.error('Failed to register data source'); } + } catch (error) { + console.error('Error:', error); + } } // Run the function @@ -101,13 +105,13 @@ npx tsx first-query.ts You should see output similar to: ```shell -🎯 Loading ATT&CK Enterprise data... +Loading ATT&CK Enterprise data... -✅ Data source registered successfully! +Data source registered successfully! -📊 Loaded 196+ techniques +Loaded 196+ techniques -🔍 First 5 techniques: +First 5 techniques: 1. OS Credential Dumping (T1003) 2. Boot or Logon Autostart Execution (T1547) 3. Process Injection (T1055) @@ -122,18 +126,18 @@ You should see output similar to: Now let's look at more details about a specific technique. Add this code to your script after the previous console.log statements: ```typescript - console.log('\n🔎 Examining a specific technique:'); + console.log('\nExamining a specific technique:'); const firstTechnique = attackDataModel.techniques[0]; - + console.log(`Name: ${firstTechnique.name}`); console.log(`ID: ${firstTechnique.external_references[0].external_id}`); console.log(`Description: ${firstTechnique.description.substring(0, 100)}...`); - + // Check if it's a subtechnique if (firstTechnique.x_mitre_is_subtechnique) { - console.log('📌 This is a sub-technique'); + console.log('This is a sub-technique'); } else { - console.log('📌 This is a parent technique'); + console.log('This is a parent technique'); } ``` @@ -142,12 +146,22 @@ Now let's look at more details about a specific technique. Add this code to your ATT&CK techniques are organized under tactics (the "why" behind adversary actions). Let's explore this relationship: ```typescript - console.log('\n🎯 Associated tactics:'); - const tactics = firstTechnique.getTactics(); - - tactics.forEach((tactic) => { - console.log(`- ${tactic.name}: ${tactic.description.substring(0, 60)}...`); - }); + console.log('\nAssociated tactics:'); + const killChainPhases = firstTechnique.kill_chain_phases || []; + const tacticShortnames = killChainPhases + .map(phase => phase.phase_name); + + const associatedTactics = attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + + if (associatedTactics.length > 0) { + associatedTactics.forEach(tactic => { + console.log(`- ${tactic.name}: ${tactic.description.replace(/\n/g, ' ').substring(0, 60)}...`); + }); + } else { + console.log('No tactics associated with this technique.'); + } ``` ## Step 7: Run Your Enhanced Script diff --git a/examples/first-query.ts b/examples/first-query.ts index 79737082..d242aad0 100644 --- a/examples/first-query.ts +++ b/examples/first-query.ts @@ -5,7 +5,7 @@ import { } from '@mitre-attack/attack-data-model'; async function exploreAttackData() { - console.log('🎯 Loading ATT&CK Enterprise data...\n'); + console.log('Loading ATT&CK Enterprise data...\n'); // Step 1: Create a data source const dataSource = new DataSourceRegistration({ @@ -20,24 +20,53 @@ async function exploreAttackData() { const uuid = await registerDataSource(dataSource); if (uuid) { - console.log('✅ Data source registered successfully!\n'); + console.log('Data source registered successfully!\n'); // Step 3: Load the data model const attackDataModel = loadDataModel(uuid); - console.log(`📊 Loaded ${attackDataModel.techniques.length} techniques\n`); + console.log(`Loaded ${attackDataModel.techniques.length} techniques\n`); // Step 4: Explore the first few techniques - console.log('🔍 First 5 techniques:'); + console.log('First 5 techniques:'); attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { console.log( `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`, ); }); + console.log('\nExamining a specific technique:'); + const firstTechnique = attackDataModel.techniques[0]; + + console.log(`Name: ${firstTechnique.name}`); + console.log(`ID: ${firstTechnique.external_references[0].external_id}`); + console.log(`Description: ${firstTechnique.description.substring(0, 100)}...`); + + // Check if it's a subtechnique + if (firstTechnique.x_mitre_is_subtechnique) { + console.log('This is a sub-technique'); + } else { + console.log('This is a parent technique'); + } + console.log('\nAssociated tactics:'); + const killChainPhases = firstTechnique.kill_chain_phases || []; + const tacticShortnames = killChainPhases + .map(phase => phase.phase_name); + + const associatedTactics = attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + + if (associatedTactics.length > 0) { + associatedTactics.forEach(tactic => { + console.log(`- ${tactic.name}: ${tactic.description.replace(/\n/g, ' ').substring(0, 60)}...`); + }); + } else { + console.log('No tactics associated with this technique.'); + } } else { - console.error('❌ Failed to register data source'); + console.error('Failed to register data source'); } } catch (error) { - console.error('❌ Error:', error); + console.error('Error:', error); } } diff --git a/examples/multi-domain.ts b/examples/multi-domain.ts new file mode 100644 index 00000000..4d5cff68 --- /dev/null +++ b/examples/multi-domain.ts @@ -0,0 +1,171 @@ +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + +async function loadAllDomains() { + console.log('Loading all ATT&CK domains...\n'); + + // Define all three domains + type DomainName = "enterprise-attack" | "mobile-attack" | "ics-attack"; + const domains: { name: DomainName; label: string }[] = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + const dataModels: { [key: string]: any } = {}; + + for (const domain of domains) { + try { + console.log(`Loading ${domain.label} domain...`); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: domain.name, + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + dataModels[domain.name] = loadDataModel(uuid); + const techniqueCount = dataModels[domain.name].techniques.length; + console.log(`${domain.label}: ${techniqueCount} techniques loaded\n`); + } + + } catch (error) { + console.error(`Failed to load ${domain.label} domain:`, error); + } + } + + return dataModels; +} + +function analyzeDomainStatistics(dataModels: { [key: string]: any }) { + console.log('Domain Comparison:\n'); + + const stats: { [key: string]: any } = {}; + + Object.keys(dataModels).forEach(domain => { + const model = dataModels[domain]; + stats[domain] = { + techniques: model.techniques.length, + tactics: model.tactics.length, + groups: model.groups.length, + software: model.malware.length + model.tools.length, + mitigations: model.mitigations.length + }; + }); + + // Display comparison table + console.log('| Metric | Enterprise | Mobile | ICS |'); + console.log('|-------------|------------|--------|-----|'); + + const metrics = ['techniques', 'tactics', 'groups', 'software', 'mitigations']; + + metrics.forEach(metric => { + const enterprise = stats['enterprise-attack']?.[metric] || 0; + const mobile = stats['mobile-attack']?.[metric] || 0; + const ics = stats['ics-attack']?.[metric] || 0; + + console.log(`| ${metric.padEnd(11)} | ${String(enterprise).padEnd(10)} | ${String(mobile).padEnd(6)} | ${String(ics).padEnd(3)} |`); + }); + + console.log('\n'); + return stats; +} + +function findCommonTactics(dataModels: { [key: string]: any }) { + console.log('Tactic Analysis:\n'); + + const tacticsByDomain: { [key: string]: Set } = {}; + + // Collect tactics from each domain + Object.keys(dataModels).forEach(domain => { + tacticsByDomain[domain] = new Set(); + dataModels[domain].tactics.forEach((tactic: any) => { + tacticsByDomain[domain].add(tactic.x_mitre_shortname); + }); + }); + + // Find common tactics + const allTactics = new Set(); + Object.values(tacticsByDomain).forEach(domainTactics => { + domainTactics.forEach(tactic => allTactics.add(tactic)); + }); + + console.log('Common tactics across domains:'); + + allTactics.forEach(tactic => { + const domains = Object.keys(tacticsByDomain).filter(domain => + tacticsByDomain[domain].has(tactic) + ); + + if (domains.length > 1) { + const domainLabels = domains.map(d => { + switch(d) { + case 'enterprise-attack': return 'Enterprise'; + case 'mobile-attack': return 'Mobile'; + case 'ics-attack': return 'ICS'; + default: return d; + } + }).join(', '); + + console.log(`- ${tactic}: ${domainLabels}`); + } + }); + + console.log('\n'); +} + +function analyzeCrossDomainTechniques(dataModels: { [key: string]: any }) { + console.log('Cross-Domain Technique Analysis:\n'); + + // Look for techniques with similar names across domains + const enterpriseTechniques = dataModels['enterprise-attack']?.techniques || []; + const mobileTechniques = dataModels['mobile-attack']?.techniques || []; + + console.log('Similar techniques between Enterprise and Mobile:'); + + let similarCount = 0; + + enterpriseTechniques.forEach((entTechnique: any) => { + mobileTechniques.forEach((mobTechnique: any) => { + // Simple name similarity check + const entName = entTechnique.name.toLowerCase(); + const mobName = mobTechnique.name.toLowerCase(); + + if (entName === mobName) { + console.log(`${entTechnique.name}`); + console.log(` Enterprise: ${entTechnique.external_references[0].external_id}`); + console.log(` Mobile: ${mobTechnique.external_references[0].external_id}\n`); + similarCount++; + } + }); + }); + + console.log(`Found ${similarCount} techniques with identical names across domains.\n`); +} + +async function performMultiDomainAnalysis() { + try { + // Load all domains + const dataModels = await loadAllDomains(); + + if (Object.keys(dataModels).length === 0) { + console.error('No domains loaded successfully'); + return; + } + + // Perform various analyses + analyzeDomainStatistics(dataModels); + findCommonTactics(dataModels); + analyzeCrossDomainTechniques(dataModels); + + console.log('Multi-domain analysis complete!'); + + } catch (error) { + console.error('Analysis failed:', error); + } +} + +// Run the analysis +performMultiDomainAnalysis(); \ No newline at end of file diff --git a/examples/relationship-explorer.ts b/examples/relationship-explorer.ts new file mode 100644 index 00000000..ffce549a --- /dev/null +++ b/examples/relationship-explorer.ts @@ -0,0 +1,295 @@ +import { registerDataSource, loadDataModel, DataSourceRegistration, AttackDataModel, TechniqueImpl } from '@mitre-attack/attack-data-model'; + +class RelationshipExplorer { + private attackDataModel: AttackDataModel; + + async initialize(): Promise { + console.log('Initializing ATT&CK Relationship Explorer...\n'); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (!uuid) throw new Error('Failed to register data source'); + + this.attackDataModel = loadDataModel(uuid); + console.log('Data loaded successfully!\n'); + } + + // We'll add exploration methods here... + + // Returns an array of tactic objects for a given technique + getTechniqueTactics(technique: any): any[] { + const killChainPhases = technique.kill_chain_phases || []; + const tacticShortnames = killChainPhases.map(phase => phase.phase_name); + + const tactics = this.attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + return tactics; + } + + exploreTechniqueTactics(): void { + console.log('TECHNIQUE-TACTIC RELATIONSHIPS\n'); + + // Find a specific technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' + ) as TechniqueImpl | undefined; + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 100)}...\n`); + + const tactics = this.getTechniqueTactics(technique); + console.log(`This technique is used for ${tactics.length} tactical goal(s):`); + + tactics.forEach((tactic, index) => { + console.log(`${index + 1}. ${tactic.name}`); + console.log(` Purpose: ${tactic.description.substring(0, 80)}...\n`); + }); + + console.log('This shows how one technique can serve multiple adversary goals!\n'); + } + + exploreGroupTechniques(): void { + console.log('GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); + + // Find APT1 group + const apt1 = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' + ); + + if (!apt1) return; + + console.log(`Group: ${apt1.name} (${apt1.external_references[0].external_id})`); + console.log(`Description: ${apt1.description.substring(0, 120)}...\n`); + + // Get techniques used by this group + const techniques = apt1.getTechniques(); + console.log(`This group uses ${techniques.length} different techniques:`); + + // Show first 5 techniques + techniques.slice(0, 5).forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Find the relationship to get procedure details + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === apt1.id && + rel.target_ref === technique.id && + rel.relationship_type === 'uses' + ); + + if (relationship && relationship.description) { + console.log(` Procedure: ${relationship.description.substring(0, 100)}...`); + } + console.log(''); + }); + + console.log(`... and ${techniques.length - 5} more techniques\n`); + } + + exploreSoftwareUsage(): void { + console.log('SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); + + // Find Mimikatz (a well-known tool) + const mimikatz = this.attackDataModel.tools.find(tool => + tool.name.toLowerCase().includes('mimikatz') + ); + + if (!mimikatz) return; + + console.log(`Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); + console.log(`Description: ${mimikatz.description.substring(0, 120)}...\n`); + + // Get techniques used by this software + const techniques = mimikatz.getTechniques(); + console.log(`This tool implements ${techniques.length} techniques:`); + + techniques.forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + + console.log(` Supports tactics: ${tactics.map(t => t.name).join(', ')}\n`); + }); + const relationships = this.attackDataModel.relationships; + + const mimikatzId = mimikatz.id; + + // Find all "uses" relationships where target is Mimikatz + const groupUsesMimikatz = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.target_ref === mimikatzId && + rel.source_ref.startsWith("intrusion-set--") // group id prefix + ); + + // Get group objects + const groupsUsingMimikatz = groupUsesMimikatz.map(rel => + this.attackDataModel.groups.find(group => group.id === rel.source_ref) + ).filter(Boolean); // Remove undefined if any + + console.log(`This tool is used by ${groupsUsingMimikatz.length} groups:`); + groupsUsingMimikatz.slice(0, 3).forEach((group, index) => { + console.log(`${index + 1}. ${group.name} (${group.external_references[0].external_id})`); + }); + console.log(''); + } + + exploreSubtechniqueRelationships(): void { + console.log('PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); + + // Find a parent technique with sub-techniques + const parentTechnique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1003' && + !t.x_mitre_is_subtechnique + ) as TechniqueImpl | undefined; + + if (!parentTechnique) return; + + console.log(`Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); + console.log(`Description: ${parentTechnique.description.substring(0, 120)}...\n`); + + // Get sub-techniques + + const subTechniques = parentTechnique.getSubTechniques(); + console.log(`This technique has ${subTechniques.length} sub-techniques:`); + + subTechniques.forEach((subTech, index) => { + console.log(`${index + 1}. ${subTech.name} (${subTech.external_references[0].external_id})`); + console.log(` Platforms: ${subTech.x_mitre_platforms?.join(', ') || 'Not specified'}\n`); + }); + + // Navigate back from sub-technique to parent + if (subTechniques.length > 0) { + const firstSubTech = subTechniques[0]; + const parentFromChild = firstSubTech.getParentTechnique(); + + console.log(`Navigation verification:`); + console.log(`Sub-technique "${firstSubTech.name}" → Parent: "${parentFromChild?.name}"`); + console.log(`Bidirectional navigation works!\n`); + } + } + + exploreMitigationRelationships(): void { + console.log('MITIGATION-TECHNIQUE RELATIONSHIPS\n'); + + // Find a technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1059' + ); + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 120)}...\n`); + + // Get mitigations for this technique + const mitigations = technique.getMitigations(); + console.log(`This technique can be mitigated by ${mitigations.length} measures:`); + + mitigations.forEach((mitigation, index) => { + console.log(`${index + 1}. ${mitigation.name} (${mitigation.external_references[0].external_id})`); + + // Find the relationship to get mitigation guidance + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === mitigation.id && + rel.target_ref === technique.id && + rel.relationship_type === 'mitigates' + ); + + if (relationship && relationship.description) { + console.log(` Guidance: ${relationship.description.substring(0, 100)}...\n`); + } + }); + } + + exploreTransitiveRelationships(): void { + console.log('TRANSITIVE RELATIONSHIPS (Group → Software → Techniques)\n'); + + // Find a group + const group = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0016' + ); + + if (!group) return; + + console.log(`Group: ${group.name} (${group.external_references[0].external_id})`); + console.log(`Description: ${group.description.substring(0, 120)}...\n`); + + const relationships = this.attackDataModel.relationships; + + // Get software used by the group + const softwareUsedByGroup = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.source_ref === group.id && + ( + rel.target_ref.startsWith("malware--") || + rel.target_ref.startsWith("tool--") + ) + ); + + // Get software objects + const allSoftware = [ + ...this.attackDataModel.malware, + ...this.attackDataModel.tools + ]; + const software = softwareUsedByGroup.map(rel => + allSoftware.find(soft => soft.id === rel.target_ref) + ).filter(Boolean); + + console.log(`This group uses ${software.length} software tools:`); + + software.slice(0, 3).forEach((soft, index) => { + console.log(`\n${index + 1}. ${soft.name} (${soft.external_references[0].external_id})`); + + // Get techniques used by this software + const techniques = soft.getTechniques(); + console.log(` → Implements ${techniques.length} techniques:`); + + techniques.slice(0, 2).forEach((technique, techIndex) => { + console.log(` ${techIndex + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show tactics supported + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + console.log(` Tactics: ${tactics.map(t => t.name).join(', ')}`); + }); + + if (techniques.length > 2) { + console.log(` ... and ${techniques.length - 2} more techniques`); + } + }); + + console.log(`\nThis shows the relationship chain: Group → Software → Techniques → Tactics\n`); + } +} + +// Initialize and run examples +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // We'll add example calls here... + await explorer.exploreTechniqueTactics(); + await explorer.exploreGroupTechniques(); + await explorer.exploreSoftwareUsage(); + await explorer.exploreSubtechniqueRelationships(); + await explorer.exploreMitigationRelationships(); + await explorer.exploreTransitiveRelationships(); + + console.log('Relationship exploration complete!\n'); + console.log('Key takeaways:'); + console.log(' - ATT&CK objects are richly interconnected'); + console.log(' - Relationships carry descriptive context'); + console.log(' - Navigation methods simplify complex queries'); + console.log(' - Transitive relationships reveal attack patterns'); +} + +main().catch(console.error); From 08bcd67ac0d072f65f51f8a8d88dc2e6b5ab0818 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 8 Sep 2025 13:51:40 -0400 Subject: [PATCH 33/39] chore(docs): add developer setup instructions --- docs/CONTRIBUTING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ffaacecb..c1f69758 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -61,9 +61,21 @@ When submitting a pull request: 5. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 6. Include a description of your changes and why they're necessary. +## Developer Setup + +### Requirements + +- [Node.js](https://nodejs.org/) v18.20 + +### Install dependences + +```bash +npm install +``` + ## Developer Workflow -To maintain code quality and consistency, we use **ESLint**, **Prettier**, and **Husky** as part of the development workflow. Below is an overview of how each tool is configured and how it affects the contribution process: +To maintain code quality and consistency, we use **ESLint**, **Prettier**, and **Husky** as part of the development workflow. These tools are installed and configured by the [Developer Setup](#developer-setup) steps above. Below is an overview of how each tool is configured and how it affects the contribution process: ### ESLint and Prettier Configuration From 086ea76eb59b85941a2e39a85850d39a3e09d148 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 8 Sep 2025 13:54:20 -0400 Subject: [PATCH 34/39] chore(docs): fix docusaurus generation instructions --- docusaurus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/README.md b/docusaurus/README.md index 475cbec6..4696c0a8 100644 --- a/docusaurus/README.md +++ b/docusaurus/README.md @@ -25,7 +25,7 @@ $ npm install The `generate-docs.sh` script is used to automate the process of converting Zod schemas into Markdown. ```bash -$ npm run autodocs +$ npm run gendocs ``` ## Local Development From 50733a3510a26d191353f79ed2d2c06498845fa7 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 12 Sep 2025 10:55:59 -0500 Subject: [PATCH 35/39] docs: remove object design rationale --- .../principles/object-design-rationale.mdx | 448 ------------------ docusaurus/sidebars.ts | 1 - 2 files changed, 449 deletions(-) delete mode 100644 docusaurus/docs/principles/object-design-rationale.mdx diff --git a/docusaurus/docs/principles/object-design-rationale.mdx b/docusaurus/docs/principles/object-design-rationale.mdx deleted file mode 100644 index 4ff47635..00000000 --- a/docusaurus/docs/principles/object-design-rationale.mdx +++ /dev/null @@ -1,448 +0,0 @@ -import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; - -# Object Design Rationale - - - -**Why ATT&CK objects are structured the way they are** - -The design of ATT&CK objects reflects careful consideration of competing requirements: STIX standards compliance, semantic accuracy, performance implications, and usability trade-offs. Each design decision represents a specific choice between alternatives, with clear rationales and acknowledged trade-offs. Understanding these decisions helps you work more effectively with ATT&CK data and make informed choices when extending the framework. - -## Design Philosophy - -### Semantic Fidelity Over Simplicity - -ATT&CK object design prioritizes accurate representation of adversary behavior concepts over implementation convenience. This philosophy manifests in several ways: - -#### Rich Metadata Preservation - -Objects retain all contextual information that might be useful for analysis, even if it increases complexity: - -```json -{ - "type": "attack-pattern", - "name": "Process Injection", - "x_mitre_platforms": ["Windows", "Linux", "macOS"], - "x_mitre_system_requirements": ["Administrator privileges may be required"], - "x_mitre_permissions_required": ["User"], - "x_mitre_effective_permissions": ["Administrator"], - "x_mitre_defense_bypassed": ["Anti-virus", "Application control"], - "x_mitre_remote_support": true -} -``` - -**Rationale**: Adversary techniques exist in complex operational contexts. Simplifying away this complexity would reduce the framework's analytical value. - -#### Explicit Relationship Modeling - -Rather than embedding relationships as simple references, ATT&CK uses explicit relationship objects that can carry their own metadata: - -```json -{ - "type": "relationship", - "relationship_type": "uses", - "source_ref": "intrusion-set--12345...", - "target_ref": "attack-pattern--67890...", - "description": "APT1 has used Process Injection to execute code within the address space of another process...", - "x_mitre_version": "1.0" -} -``` - -**Benefits**: Procedure descriptions, confidence levels, and temporal information can be associated with specific technique usage patterns. - -**Trade-offs**: More complex query patterns and increased memory usage compared to embedded references. - -### Standards Compliance Over Custom Optimization - -Object designs maintain STIX 2.1 compliance even when custom formats might be more efficient or convenient. - -#### STIX ID Requirements - -Every object must have a globally unique STIX ID, even though ATT&CK IDs would be more human-readable: - -```json -{ - "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "T1055", - "url": "https://attack.mitre.org/techniques/T1055" - } - ] -} -``` - -**Rationale**: STIX ID uniqueness enables reliable cross-system integration and prevents identifier collisions. - -**Trade-offs**: Less intuitive programmatic access patterns compared to human-readable identifiers. - -## Object Type Decisions - -### Techniques as attack-pattern Objects - -**Decision**: Techniques use the standard STIX `attack-pattern` type rather than a custom type. - -**Rationale**: - -- Techniques represent specific methods of attack, which aligns perfectly with STIX's `attack-pattern` concept -- Leverages existing STIX tooling and analyst familiarity -- Avoids unnecessary custom types when standard STIX types suffice - -**Alternative considered**: Custom `x-mitre-technique` type -**Why rejected**: Would duplicate standard STIX functionality without adding semantic value - -**Implementation details**: - -```json -{ - "type": "attack-pattern", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "execution" - } - ], - "x_mitre_is_subtechnique": false -} -``` - -### Tactics as Custom Objects - -**Decision**: Tactics use a custom `x-mitre-tactic` type rather than extending an existing STIX type. - -**Rationale**: - -- Tactics represent adversary goals/objectives, which don't map cleanly to any standard STIX type -- The closest STIX equivalent (`x_mitre_tactic`) would require awkward semantic stretching -- Custom type allows clear semantic definition and appropriate properties - -**Alternative considered**: Extending `attack-pattern` or creating tactic-specific `kill-chain-phase` definitions -**Why rejected**: Tactics are fundamentally different from attack patterns and deserve their own semantic space - -**Implementation details**: - -```json -{ - "type": "x-mitre-tactic", - "x_mitre_shortname": "execution", - "name": "Execution", - "description": "The adversary is trying to run malicious code." -} -``` - -### Groups as intrusion-set Objects - -**Decision**: Groups use the standard STIX `intrusion-set` type. - -**Rationale**: - -- Threat actor groups align precisely with STIX's `intrusion-set` concept -- Leverages standard STIX properties for attribution, motivation, and sophistication -- Enables integration with threat intelligence feeds using the same object type - -**Implementation details**: - -```json -{ - "type": "intrusion-set", - "name": "APT1", - "aliases": ["Comment Crew", "PLA Unit 61398"], - "first_seen": "2006-01-01T00:00:00.000Z" -} -``` - -### Software as malware/tool Objects - -**Decision**: Software uses standard STIX `malware` and `tool` types based on malicious intent. - -**Rationale**: - -- Distinguishes between software created for malicious purposes (`malware`) and legitimate tools used maliciously (`tool`) -- Aligns with existing security industry classification practices -- Leverages standard STIX properties for software analysis - -**Classification criteria**: - -- **malware**: Software created primarily for malicious purposes -- **tool**: Legitimate software that can be used for malicious purposes - -**Alternative considered**: Single custom `x-mitre-software` type -**Why rejected**: Would lose the important semantic distinction between purpose-built malware and dual-use tools - -### Mitigations as course-of-action Objects - -**Decision**: Mitigations use the standard STIX `course-of-action` type. - -**Rationale**: - -- Defensive recommendations align perfectly with STIX's `course-of-action` concept -- No custom properties needed beyond standard STIX fields -- Enables integration with broader defensive planning and STIX-based defensive frameworks - -### Sub-techniques as Specialized attack-patterns - -**Decision**: Sub-techniques use the same `attack-pattern` type as parent techniques, distinguished by properties and relationships. - -**Design pattern**: - -```json -{ - "type": "attack-pattern", - "x_mitre_is_subtechnique": true, - // Connected via subtechnique-of relationship -} -``` - -**Rationale**: - -- Sub-techniques are specialized techniques, not fundamentally different objects -- Inheritance of properties and behaviors from parent techniques is natural -- Avoids artificial distinction between technique levels - -**Alternative considered**: Custom `x-mitre-subtechnique` type -**Why rejected**: Would create artificial barriers to code reuse and conceptual understanding - -## Property Design Patterns - -### The x_mitre_ Namespace Strategy - -**Decision**: All ATT&CK-specific properties use the `x_mitre_` prefix. - -**Benefits**: - -- Clearly identifies ATT&CK extensions from standard STIX properties -- Prevents naming conflicts with future STIX standard properties -- Signals custom property status to STIX-compliant parsers -- Enables selective processing of standard vs. extended properties - -**Implementation pattern**: - -```json -{ - "name": "Process Injection", // Standard STIX - "description": "Adversaries may inject...", // Standard STIX - "x_mitre_platforms": ["Windows"], // ATT&CK extension - "x_mitre_version": "1.2" // ATT&CK extension -} -``` - -### Platform Property Design - -**Decision**: Platforms are represented as string arrays rather than enumerated types or structured objects. - -**Rationale**: - -- Flexibility to add new platforms without schema changes -- Simple querying and filtering patterns -- Aligns with how analysts naturally think about platform applicability - -**Alternative considered**: Structured platform objects with version and edition details -**Why rejected**: Added complexity without clear analytical benefit for most use cases - -**Implementation**: - -```json -{ - "x_mitre_platforms": ["Windows", "Linux", "macOS", "Android", "iOS"] -} -``` - -### Version Property Design - -**Decision**: Object versions use semantic versioning strings (`"1.0"`, `"1.1"`, `"2.0"`). - -**Rationale**: - -- Human-readable version comparisons -- Standard semantic versioning practices -- Enables version-aware processing and compatibility checks - -**Alternative considered**: Integer version numbers or timestamp-based versioning -**Why rejected**: Less expressive for indicating the magnitude of changes - -### Boolean vs. Enumerated Properties - -**Decision**: Use boolean properties for binary distinctions (`x_mitre_is_subtechnique`) and string arrays for multi-valued properties (`x_mitre_platforms`). - -**Design principle**: Match the property type to the natural cardinality of the concept: - -```json -{ - "x_mitre_is_subtechnique": true, // Binary distinction - "x_mitre_platforms": ["Windows", "Linux"], // Multi-valued - "x_mitre_remote_support": false // Binary capability -} -``` - -## Relationship Design Patterns - -### Explicit vs. Embedded Relationships - -**Decision**: Use explicit STIX relationship objects rather than embedded references. - -**Pattern**: - -```json -// ❌ Embedded approach -{ - "type": "attack-pattern", - "mitigated_by": ["course-of-action--12345...", "course-of-action--67890..."] -} - -// ✅ Explicit relationship approach -{ - "type": "relationship", - "relationship_type": "mitigates", - "source_ref": "course-of-action--12345...", - "target_ref": "attack-pattern--67890...", - "description": "Specific guidance on how this mitigation applies..." -} -``` - -**Benefits**: - -- Relationships can carry metadata (descriptions, confidence, temporal information) -- Bidirectional navigation is naturally supported -- Relationship evolution doesn't require object schema changes -- Complex relationship patterns are easily represented - -**Trade-offs**: - -- Increased memory usage for relationship storage -- More complex query patterns for relationship traversal -- Additional processing overhead for relationship resolution - -### Custom Relationship Types - -**Decision**: Define custom relationship types for ATT&CK-specific associations. - -**Examples**: - -- `subtechnique-of`: Links sub-techniques to parent techniques -- `detects`: Links detection strategies to techniques -- `mitigates`: Links defensive measures to techniques (standard STIX but worth noting) - -**Rationale**: ATT&CK relationships have specific semantics that generic relationship types cannot capture accurately. - -### Relationship Direction Consistency - -**Decision**: Establish consistent direction patterns for relationship types: - -- `subtechnique-of`: sub-technique → parent technique -- `detects`: detection capability → technique -- `mitigates`: defensive measure → technique -- `uses`: actor/software → technique/software - -**Benefits**: Predictable query patterns and consistent mental models for relationship navigation. - -## Performance vs. Semantics Trade-offs - -### Memory Usage Decisions - -**Choice**: Prioritize semantic accuracy over memory efficiency. - -**Implications**: - -- Rich metadata is preserved even if rarely used -- Explicit relationships consume more memory than embedded references -- Full object graphs are maintained rather than lazy-loading references - -**Rationale**: ATT&CK is primarily used for analysis rather than high-volume transaction processing, so semantic richness typically outweighs memory concerns. - -### Query Complexity Decisions - -**Choice**: Accept query complexity in exchange for relationship flexibility. - -**Example**: Finding all mitigations for a technique requires relationship traversal: - -```typescript -// Complex but flexible -const mitigations = bundle.objects - .filter(obj => - obj.type === 'relationship' && - obj.relationship_type === 'mitigates' && - obj.target_ref === technique.id - ) - .map(rel => bundle.objects.find(obj => obj.id === rel.source_ref)); - -// vs. simple but inflexible embedded approach -const mitigations = technique.mitigated_by.map(id => - bundle.objects.find(obj => obj.id === id) -); -``` - -**Mitigation**: The ATT&CK Data Model library pre-processes relationships into indexes to restore O(1) lookup performance. - -### Validation Complexity Decisions - -**Choice**: Implement comprehensive validation at the cost of processing overhead. - -**Rationale**: Data quality issues in ATT&CK datasets can cascade through analysis workflows, making upfront validation cost-effective despite performance overhead. - -**Implementation strategy**: Provide both strict and relaxed validation modes to balance quality and performance based on use case requirements. - -## Extension Point Design - -### Custom Property Extensibility - -**Decision**: Allow arbitrary custom properties following STIX naming conventions. - -**Pattern**: - -```json -{ - "type": "attack-pattern", - "name": "Process Injection", - "x_mitre_platforms": ["Windows"], // Standard ATT&CK extension - "x_org_threat_level": "high", // Organization-specific extension - "x_custom_last_observed": "2023-01-15" // Custom tracking property -} -``` - -**Benefits**: Organizations can add domain-specific metadata without breaking standards compliance. - -**Constraints**: Custom properties must follow STIX naming conventions (`x_` prefix) and not conflict with existing properties. - -### Schema Evolution Patterns - -**Decision**: Use additive schema evolution with explicit deprecation cycles. - -**Process**: - -1. New properties are added as optional fields -2. Deprecated properties are marked but remain functional -3. Removal occurs only at major version boundaries with migration guidance - -**Benefits**: Backward compatibility is maintained while enabling specification evolution. - -## Living with Design Complexity - -### When Object Design Feels Overwhelming - -ATT&CK object design can seem complex because it encodes the full semantic richness of adversary behavior analysis. This complexity is intentional and necessary: - -- **STIX compliance** requires numerous standard properties and relationships -- **Semantic accuracy** demands precise representation of adversary concepts -- **Extensibility** needs mechanisms that maintain standards compliance -- **Performance** optimization requires careful trade-off management - -### Simplification Strategies - -When working with ATT&CK objects: - -1. **Use implementation classes** - Higher-level APIs abstract object complexity -2. **Focus on your use case** - You rarely need to understand all object properties -3. **Leverage validation feedback** - Let schema validation guide correct usage -4. **Start with examples** - Working code demonstrates proper object usage patterns - -### Contributing to Object Design - -Understanding these design rationales helps you: - -- **Propose extensions** that align with existing patterns -- **Identify genuine design problems** vs. complexity that serves a purpose -- **Suggest performance improvements** that preserve semantic accuracy -- **Evaluate trade-offs** when extending the framework - ---- diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index d240e490..560c2f9f 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -103,7 +103,6 @@ const sidebars: SidebarsConfig = { 'principles/why-zod', 'principles/stix-foundation', 'principles/attack-specification-overview', - 'principles/object-design-rationale', 'principles/versioning-philosophy', 'principles/schema-design', 'principles/compatibility', From 4cd8d741a9d9861196d7988e0e163a8cea1d0e4e Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 12 Sep 2025 10:58:05 -0500 Subject: [PATCH 36/39] docs: add placeholder for why we chose typescript --- docusaurus/docs/principles/index.mdx | 2 +- docusaurus/docs/principles/why-adm-exists.mdx | 27 +++++++++---------- docusaurus/docs/principles/why-typescript.mdx | 0 3 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 docusaurus/docs/principles/why-typescript.mdx diff --git a/docusaurus/docs/principles/index.mdx b/docusaurus/docs/principles/index.mdx index e445206a..56373c39 100644 --- a/docusaurus/docs/principles/index.mdx +++ b/docusaurus/docs/principles/index.mdx @@ -14,13 +14,13 @@ These explanations provide context and rationale rather than instructions, helpi ### Foundational Context - **[Why the ATT&CK Data Model Exists](./why-adm-exists)** - The problem context, solution approach, and value proposition +- **[Why TypeScript Instead of X](./why-typescript)** - Why we chose TypeScript - **[Why Zod Instead of X](./why-zod)** - Why we chose Zod over other options - **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX was chosen and how it shapes the architecture ### ATT&CK Specification Understanding - **[ATT&CK Specification Overview](./attack-specification-overview)** - Understanding the structure and purpose of the ATT&CK specification -- **[Object Design Rationale](./object-design-rationale)** - Why ATT&CK objects are structured the way they are - **[Versioning Philosophy](./versioning-philosophy)** - Understanding ATT&CK's multi-dimensional versioning approach ### Technical Architecture diff --git a/docusaurus/docs/principles/why-adm-exists.mdx b/docusaurus/docs/principles/why-adm-exists.mdx index bb939f6f..ec55b802 100644 --- a/docusaurus/docs/principles/why-adm-exists.mdx +++ b/docusaurus/docs/principles/why-adm-exists.mdx @@ -4,11 +4,10 @@ import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; -**The problem context, solution approach, and value proposition** +The ATT&CK Data Model library didn't emerge from a vacuum - it was created to solve specific, recurring problems that users face when working with MITRE ATT&CK data. +Understanding these problems and the solution approach helps clarify when and why to use this library. -The ATT&CK Data Model library didn't emerge in a vacuum - it was created to solve specific, recurring problems that security teams and researchers face when working with MITRE ATT&CK data. Understanding these problems and the solution approach helps clarify when and why to use this library. - -## The Problem Context +## The Problem ### ATT&CK Data Complexity @@ -16,8 +15,8 @@ MITRE ATT&CK, while invaluable for threat intelligence and security operations, **Raw Data Challenges**: -- ATT&CK data is distributed as STIX 2.1 JSON bundles containing hundreds of objects -- Relationships between objects are expressed through separate relationship objects, not direct references +- ATT&CK data is distributed as STIX 2.1 JSON bundles containing thousands of objects +- Relationships between objects are primarily expressed through separate relationship objects, not direct references - Objects have complex validation rules that aren't immediately obvious from the JSON structure - Different ATT&CK domains (Enterprise, Mobile, ICS) have subtle but important differences @@ -25,7 +24,6 @@ MITRE ATT&CK, while invaluable for threat intelligence and security operations, - No built-in type safety - easy to access non-existent properties or use wrong data types - Relationship navigation requires manual lookup and cross-referencing -- Validation errors are cryptic and don't point to specific ATT&CK requirements - No clear patterns for common operations like "find all techniques used by a group" ### Before the ATT&CK Data Model @@ -85,16 +83,15 @@ class AttackDataParser { ### The Core Problems -Through observing these patterns across security teams, several core problems emerged: +Through observing these patterns across users, several core problems emerged: -1. **Repetitive Boilerplate**: Every team reimplemented the same relationship navigation logic -2. **Validation Gaps**: Data quality issues only discovered at runtime, often in production -3. **Type Safety Absence**: No compile-time guarantees about data structure -4. **Knowledge Duplication**: ATT&CK domain knowledge scattered across individual implementations -5. **Version Compatibility**: Difficulty updating to new ATT&CK versions -6. **Testing Challenges**: Hard to test applications against different ATT&CK datasets +1. **Repetitive Boilerplate**: Every team and tool reimplemented the same relationship navigation logic +2. **Validation Gaps**: Data quality issues only discovered at runtime +3. **Knowledge Duplication**: ATT&CK domain knowledge scattered across individual implementations +4. **Version Compatibility**: Difficulty updating to new ATT&CK versions +5. **Testing Challenges**: Hard to test applications against different ATT&CK datasets -## The Solution Approach +## The Solution ### Design Philosophy diff --git a/docusaurus/docs/principles/why-typescript.mdx b/docusaurus/docs/principles/why-typescript.mdx new file mode 100644 index 00000000..e69de29b From 8633983001ab4db7b8f1be70d5b39b6d80ecd711 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:50:15 -0400 Subject: [PATCH 37/39] Merge branch 'main' into doc-cleanup --- docs/COMPATIBILITY.md | 20 +- docs/SPEC.md | 145 +++++----- src/classes/attack-data-model.ts | 29 +- src/classes/sdo/data-component.impl.ts | 14 - src/classes/sdo/index.ts | 1 - src/classes/sdo/log-source.impl.ts | 24 -- src/classes/sdo/technique.impl.ts | 16 +- src/generator/index.ts | 91 ++++--- src/main.ts | 4 - src/refinements/index.ts | 46 ---- src/schemas/common/attack-id.ts | 7 - src/schemas/common/stix-type.ts | 2 - src/schemas/sdo/analytic.schema.ts | 41 ++- src/schemas/sdo/data-component.schema.ts | 53 +++- src/schemas/sdo/index.ts | 17 +- src/schemas/sdo/log-source.schema.ts | 80 ------ src/schemas/sdo/stix-bundle.schema.ts | 128 ++++++++- src/schemas/sro/relationship.schema.ts | 28 +- test/objects/analytic.test.ts | 145 +++++----- test/objects/data-component.test.ts | 147 ++++++++++- test/objects/log-source.test.ts | 322 ----------------------- 21 files changed, 593 insertions(+), 767 deletions(-) delete mode 100644 src/classes/sdo/log-source.impl.ts delete mode 100644 src/schemas/sdo/log-source.schema.ts delete mode 100644 test/objects/log-source.test.ts diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index d78725c0..d6d2a12f 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -14,16 +14,16 @@ This document tracks the compatibility between versions of the ATT&CK® Data Mod ## Compatibility Details - **ATT&CK Specification v3.3.0**: - - **Features**: - - Introduces support for three new SDOs: - - MITRE Detection Strategies (`x-mitre-detection-strategy`) [[schema](../src/schemas/sdo/detection-strategy.schema.ts)] - - MITRE Analytics (`x-mitre-analytic`) [[schema](../src/schemas/sdo/analytic.schema.ts)] - - MITRE Log Sources (`x-mitre-log-sources`) [[schema](../src/schemas/sdo/log-source.schema.ts)] - - MITRE Data Sources (`x-mitre-data-sources`) [[schema](../src/schemas/sdo/data-source.schema.ts)] are _deprecated_ and will be removed in ATT&CK Specification v4.0.0 - - Supports all content introduced in ATT&CK Release v18.x and earlier, including new techniques, tactics, and relationships. - - **Notes**: - - Ensure that you are using the correct domain (e.g., `enterprise-attack`, `mobile-attack`) when loading data to avoid inconsistencies. - - The ADM's Zod schemas and TypeScript types are aligned with the ATT&CK Specification v4.0.0, providing a codified expression of the specification. + - **New SDOs**: + - MITRE Detection Strategies (`x-mitre-detection-strategy`) [[schema](../src/schemas/sdo/detection-strategy.schema.ts)] + - MITRE Analytics (`x-mitre-analytic`) [[schema](../src/schemas/sdo/analytic.schema.ts)] + - **Schema Changes**: + - Added `x_mitre_log_sources` field to MITRE Data Components for tracking security telemetry across platforms + - **Deprecations**: + - MITRE Data Sources (`x-mitre-data-sources`) [[schema](../src/schemas/sdo/data-source.schema.ts)] (removal in v4.0.0) + - `x-mitre-data-component` --[detects]--> `attack-pattern` SROs (replaced by Detection Strategy framework) + - **Content Support**: + - ATT&CK Release v18.x and later ## Using Other Versions diff --git a/docs/SPEC.md b/docs/SPEC.md index 4e3322ab..5f319e67 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -30,7 +30,6 @@ ATT&CK uses a mix of predefined and custom STIX objects to implement ATT&CK conc | [Asset](#assets) | `x-mitre-asset` | yes | | [Detection Strategy](#detection-strategies) 3 | `x-mitre-detection-strategy` | yes | | [Analytic](#analytics) 3 | `x-mitre-analytic` | yes | -| [Log Source](#log-sources) 3 | `x-mitre-log-source` | yes | 1 This type was added in the upgrade to STIX 2.1 and is not available in [the STIX 2.0 dataset](https://github.com/mitre/cti). @@ -257,7 +256,7 @@ Data sources and data components define the telemetry and observational data tha - Each data component has exactly one parent data source - Data sources can contain multiple data components - Data components can map to multiple techniques via `detects` relationships (deprecated as of 3.3.0) -- Data components can map to log sources via `found-in` relationships (new in 3.3.0) +- Data components now contain embedded log source configurations in their `x_mitre_log_sources` field **Architecture overview:** @@ -281,7 +280,10 @@ Data sources and data components define the telemetry and observational data tha **Legacy compatibility:** Prior to ATT&CK v10, data sources were stored in the `x_mitre_data_sources` field on techniques. This field remains available for backward compatibility and accurately reflects current data sources, but lacks the granularity of the component-based model. **The ICS domain continues to use only the legacy `x_mitre_data_sources` field.** -#### Data Sources +#### Data Sources (Deprecated) + +> [!WARNING] +> **Deprecation Notice**: Data Sources (`x-mitre-data-source`) are deprecated as of ATT&CK Specification 3.3.0 and superseded by the Detection Strategy framework. They remain supported for backward compatibility but will be removed in ATT&CK Specification 4.0.0. Data Components remain supported and are integrated into the new Detection Strategy framework through Log Sources. Data sources represent categories of information that can be collected for detection purposes. They are defined as `x-mitre-data-source` objects extending the generic [STIX Domain Object pattern](https://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230920). @@ -301,6 +303,25 @@ Data components represent specific types of information within a data source tha | Field | Type | Description | | :------------------------ | :----------------------------- | ------------------------------------------------------- | | `x_mitre_data_source_ref` | embedded relationship (string) | STIX ID of the data source this component is a part of. | +| `x_mitre_log_sources` | `log_source[]` | Array of log source objects containing platform-specific collection configurations. | + +#### Log Source Subtype + +The `log_source` object defines platform-specific collection configurations embedded within data components: + +| Field | Type | Description | +| --------- | ------ | -------------------------------------------------------------------- | +| `name` | string | Log source identifier (e.g., "sysmon", "auditd") | +| `channel` | string | Specific log channel or event type (e.g., "1" for Sysmon Process Creation) | + +**Uniqueness constraints:** +- Each `(name, channel)` tuple must be unique within a data component's `x_mitre_log_sources` array +- Log sources are scoped to their containing data component + +**Example:** A data component for 'Process Creation' might contain log sources for: +- Windows: (name: "sysmon", channel: "1") +- Linux: (name: "auditd", channel: "SYSCALL") +- macOS: (name: "unified_logs", channel: "process") ### Campaigns @@ -369,30 +390,55 @@ The following diagrams illustrate how Detection Strategies connect to other ATT& │ "Soft" relationship │ (STIX ID reference) │ -┌──────────────▼────────────────┐ -│ │ -│ <> │ -│ Analytic │ -│ │ -│┌─────────────────────────────┐│ -││x_mitre_log_source_references││ -│└─────────────┬───────────────┘│ -└──────────────┼────────────────┘ - │ - │ "Soft" relationship - │ (array of objects: - │ { - │ x_mitre_log_source_ref: (Stix ID reference), - │ permutation_names: array - │ } - │ ) - │ - ┌─────▼──────┐ - │ │ - │ <> │ - │ Log Source │ - │ │ - └────────────┘ +┌──────────────▼─────────────────────────┐ +│ │ +│ <> │ +│ Analytic │ +│ │ +│┌──────────────────────────────────────┐│ +││ x_mitre_log_source_references: ││ +││ ││ +││ [ ││ +││ { ││ +││ x_mitre_data_component_ref, ││ +││ name: "sysmon", ││ +││ channel: "1" ││ +││ }, ││ +││ { ││ +││ x_mitre_data_component_ref, ││ +││ name: "auditd", ││ +││ channel: "SYSCALL" ││ +││ } ││ +││ ] ││ +│└──────────────┬───────────────────────┘│ +└───────────────┼────────────────────────┘ + │ + │ "Soft" relationships + │ (x_mitre_data_component_ref points to + │ Data Component; name & channel index + │ into matching log source within that + │ Data Component) + │ + ┌────────▼────────────────┐ + │ │ + │ <> │ + │ Data Component │ + │ │ + │┌───────────────────────┐│ + ││ x_mitre_log_sources: ││ + ││ ││ + ││ [ ││ + ││ { ││ + ││ name: "sysmon", ││ + ││ channel: "1" ││ + ││ }, ││ + ││ { ││ + ││ name: "auditd", ││ + ││ channel: "SYSCALL"││ + ││ } ││ + ││ ] ││ + │└───────────────────────┘│ + └─────────────────────────┘ ``` ### Analytics @@ -404,17 +450,18 @@ Analytics contain platform-specific detection logic and represent the implementa | Field | Type | Description | | :------------------------- | :---------------------- | -------------------------------------------------------------------------------------- | | `x_mitre_platforms` | string[] (max 1) | Target platform for this analytic (Windows, Linux, macOS). | -| `x_mitre_log_source_references` | `log_source_reference[]` | Array of log source references with specific permutation names. | +| `x_mitre_log_source_references` | `log_source_reference[]` | Array of data component references with specific log source details. | | `x_mitre_mutable_elements` | `mutable_element[]` | Array of tunable detection parameters for environment-specific adaptation. | #### Log Source Reference Subtype -The `log_source_reference` object links analytics to specific log source permutations: +The `log_source_reference` object links analytics to specific data components with log source details: -| Field | Type | Description | -| ------ | -------- | ------------------------------------------------------------------------------------- | -| `x_mitre_log_source_ref` | string | STIX ID of the referenced `x-mitre-log-source` object | -| `permutation_names` | string[] | Specific permutation names from the log source's `x_mitre_log_source_permutations` | +| Field | Type | Description | +| ---------------------------- | ------ | ------------------------------------------------------------------------------------- | +| `x_mitre_data_component_ref` | string | STIX ID of the referenced `x-mitre-data-component` object | +| `name` | string | Log source name from the data component's `x_mitre_log_sources` array | +| `channel` | string | Log source channel from the data component's `x_mitre_log_sources` array | #### Mutable Element Subtype @@ -425,37 +472,6 @@ The `mutable_element` object defines tunable parameters within analytics: | `field` | string | Name of the detection field that can be tuned | | `description` | string | Rationale for tunability and environment-specific considerations | -### Log Sources - -Log sources define immutable configurations for collecting security telemetry across different platforms and deployment scenarios. They are defined as `x-mitre-log-source` objects extending the generic [STIX Domain Object pattern](https://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230920). - -**Log source-specific fields:** - -| Field | Type | Description | -| :------------------------------- | :---------------------------- | -------------------------------------------------------------- | -| `x_mitre_log_source_permutations` | `log_source_permutation[]` | Array of platform-specific log collection configurations. | - -**Key characteristics:** - -- Each log source contains multiple permutations for different deployment scenarios -- Permutations represent different ways to collect the same type of data across platforms -- Connected to data components via `found-in` relationships - -#### Log Source Permutation Subtype - -The `log_source_permutation` object defines platform-specific collection configurations: - -| Field | Type | Description | -| --------- | ------ | -------------------------------------------------------------------- | -| `name` | string | Log source identifier (e.g., "sysmon", "auditd") | -| `channel` | string | Specific log channel or event type (e.g., "1" for Sysmon Process Creation) | -| `data_component_name` | string | Name of the specific data component. | - -**Example:** A single log source for 'Process Creation' might contain permutations for: - -- Windows: (name: "sysmon", channel: "1") -- Linux: (name: "auditd", channel: "SYSCALL") -- macOS: (name: "unified_logs", channel: "process") ### Relationships @@ -477,7 +493,6 @@ Relationship objects frequently include `description` fields that provide contex | `attack-pattern` | `subtechnique-of` | `attack-pattern` | Yes | Sub-technique of a technique, where the `source_ref` is the sub-technique and the `target_ref` is the parent technique. | | `x-mitre-data-component` | `detects` | `attack-pattern` | Yes | **Deprecated as of ATT&CK Specification 3.3.0.** Data component detecting a technique. This relationship type will be removed in ATT&CK Specification 4.0.0. | | `x-mitre-detection-strategy` | `detects` | `attack-pattern` | Yes | Detection strategy for detecting a technique. | -| `x-mitre-data-component` | `found-in` | `x-mitre-log-source` | Yes | Data component telemetry found in a log source. | | `attack-pattern` | `targets` | `x-mitre-asset` | Yes | Technique targets an asset. | | any type | `revoked-by` | any type | Yes | The target object is a replacement for the source object. Only occurs where the objects are of the same type, and the source object will have the property `revoked = true`. See [Working with deprecated and revoked objects](#working-with-deprecated-and-revoked-objects) for more information on revoked objects. | diff --git a/src/classes/attack-data-model.ts b/src/classes/attack-data-model.ts index 749e476c..9825633d 100644 --- a/src/classes/attack-data-model.ts +++ b/src/classes/attack-data-model.ts @@ -1,30 +1,31 @@ // Import Types +import type { Analytic } from '@/schemas/sdo/analytic.schema.js'; import type { Asset } from '@/schemas/sdo/asset.schema.js'; import type { Campaign } from '@/schemas/sdo/campaign.schema.js'; import type { Collection } from '@/schemas/sdo/collection.schema.js'; import type { DataComponent } from '@/schemas/sdo/data-component.schema.js'; import type { DataSource } from '@/schemas/sdo/data-source.schema.js'; +import type { DetectionStrategy } from '@/schemas/sdo/detection-strategy.schema.js'; import type { Group } from '@/schemas/sdo/group.schema.js'; import type { Identity } from '@/schemas/sdo/identity.schema.js'; import type { Malware } from '@/schemas/sdo/malware.schema.js'; import type { Matrix } from '@/schemas/sdo/matrix.schema.js'; import type { Mitigation } from '@/schemas/sdo/mitigation.schema.js'; +import type { AttackObject } from '@/schemas/sdo/stix-bundle.schema.js'; import type { Tactic } from '@/schemas/sdo/tactic.schema.js'; import type { Technique } from '@/schemas/sdo/technique.schema.js'; import type { Tool } from '@/schemas/sdo/tool.schema.js'; import type { MarkingDefinition } from '@/schemas/smo/marking-definition.schema.js'; import type { Relationship } from '@/schemas/sro/relationship.schema.js'; -import type { AttackObject } from '@/schemas/sdo/stix-bundle.schema.js'; -import type { LogSource } from '@/schemas/sdo/log-source.schema.js'; -import type { DetectionStrategy } from '@/schemas/sdo/detection-strategy.schema.js'; -import type { Analytic } from '@/schemas/sdo/analytic.schema.js'; // Import ES6 Classes +import { AnalyticImpl } from './sdo/analytic.impl.js'; import { AssetImpl } from './sdo/asset.impl.js'; import { CampaignImpl } from './sdo/campaign.impl.js'; import { CollectionImpl } from './sdo/collection.impl.js'; import { DataComponentImpl } from './sdo/data-component.impl.js'; import { DataSourceImpl } from './sdo/data-source.impl.js'; +import { DetectionStrategyImpl } from './sdo/detection-strategy.impl.js'; import { GroupImpl } from './sdo/group.impl.js'; import { IdentityImpl } from './sdo/identity.impl.js'; import { MalwareImpl } from './sdo/malware.impl.js'; @@ -35,9 +36,6 @@ import { TechniqueImpl } from './sdo/technique.impl.js'; import { ToolImpl } from './sdo/tool.impl.js'; import { MarkingDefinitionImpl } from './smo/marking-definition.impl.js'; import { RelationshipImpl } from './sro/relationship.impl.js'; -import { LogSourceImpl } from './sdo/log-source.impl.js'; -import { DetectionStrategyImpl } from './sdo/detection-strategy.impl.js'; -import { AnalyticImpl } from './sdo/analytic.impl.js'; export class AttackDataModel { public techniques: TechniqueImpl[] = []; @@ -55,7 +53,6 @@ export class AttackDataModel { public matrices: MatrixImpl[] = []; public collections: CollectionImpl[] = []; public relationships: RelationshipImpl[] = []; - public logSources: LogSourceImpl[] = []; public detectionStrategies: DetectionStrategyImpl[] = []; public analytics: AnalyticImpl[] = []; @@ -208,14 +205,6 @@ export class AttackDataModel { break; } - // LOG SOURCE - case 'x-mitre-log-source': { - const logSource = new LogSourceImpl(object as LogSource); - this.logSources.push(logSource); - objectMap.set(object.id, logSource); - break; - } - // DETECTION STRATEGY case 'x-mitre-detection-strategy': { const detectionStrategy = new DetectionStrategyImpl(object as DetectionStrategy); @@ -309,13 +298,6 @@ export class AttackDataModel { } break; - case 'found-in': - if (sourceObj instanceof DataComponentImpl && targetObj instanceof LogSourceImpl) { - sourceObj.addFoundIn(targetObj); - targetObj.addFoundBy(sourceObj); - } - break; - default: break; } @@ -342,6 +324,5 @@ export type AnyAttackObject = | MitigationImpl | RelationshipImpl | MarkingDefinitionImpl - | LogSourceImpl | DetectionStrategyImpl | AnalyticImpl; diff --git a/src/classes/sdo/data-component.impl.ts b/src/classes/sdo/data-component.impl.ts index fe86992f..822cd48f 100644 --- a/src/classes/sdo/data-component.impl.ts +++ b/src/classes/sdo/data-component.impl.ts @@ -2,12 +2,10 @@ import type { DataComponent } from '../../schemas/sdo/data-component.schema.js'; import { AttackBaseImpl } from '../common/attack-object.impl.js'; -import { LogSourceImpl } from './log-source.impl.js'; import { TechniqueImpl } from './technique.impl.js'; export class DataComponentImpl extends AttackBaseImpl implements DataComponent { private _detectedTechniques: TechniqueImpl[] = []; - private _logSources: LogSourceImpl[] = []; constructor(readonly dataComponent: DataComponent) { super(); @@ -19,22 +17,10 @@ export class DataComponentImpl extends AttackBaseImpl implements DataComponent { this._detectedTechniques.push(technique); } - addFoundIn(logSource: LogSourceImpl): void { - this._logSources.push(logSource); - } - // Getters getDetectedTechniques(): TechniqueImpl[] { return this._detectedTechniques; } - - getLogSources(): LogSourceImpl[] { - return this._logSources; - } - - get foundIn(): LogSourceImpl[] { - return this._logSources; - } } // Suppress the lint error for the empty interface diff --git a/src/classes/sdo/index.ts b/src/classes/sdo/index.ts index f4069f14..5398d0e6 100644 --- a/src/classes/sdo/index.ts +++ b/src/classes/sdo/index.ts @@ -11,6 +11,5 @@ export * from './mitigation.impl.js'; export * from './tactic.impl.js'; export * from './technique.impl.js'; export * from './tool.impl.js'; -export * from './log-source.impl.js'; export * from './detection-strategy.impl.js'; export * from './analytic.impl.js'; diff --git a/src/classes/sdo/log-source.impl.ts b/src/classes/sdo/log-source.impl.ts deleted file mode 100644 index 7932191f..00000000 --- a/src/classes/sdo/log-source.impl.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ - -import type { LogSource } from '../../schemas/sdo/log-source.schema.js'; -import { AttackBaseImpl } from '../common/attack-object.impl.js'; -import type { DataComponentImpl } from './data-component.impl.js'; - -export class LogSourceImpl extends AttackBaseImpl { - private _dataComponents: DataComponentImpl[] = []; - - constructor(readonly logSource: LogSource) { - super(); - Object.assign(this, logSource); - } - - addFoundBy(dataComponent: DataComponentImpl): void { - this._dataComponents.push(dataComponent); - } -} - -// Suppress the lint error for the empty interface -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface LogSourceImpl extends LogSource {} - -/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */ diff --git a/src/classes/sdo/technique.impl.ts b/src/classes/sdo/technique.impl.ts index 41c1b8ab..eed0ed98 100644 --- a/src/classes/sdo/technique.impl.ts +++ b/src/classes/sdo/technique.impl.ts @@ -1,18 +1,16 @@ /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ import type { Technique } from '../../schemas/sdo/technique.schema.js'; -import { TacticImpl } from './tactic.impl.js'; -import { MitigationImpl } from './mitigation.impl.js'; -import { LogSourceImpl } from './log-source.impl.js'; -import { AssetImpl } from './asset.impl.js'; import { AttackBaseImpl } from '../common/attack-object.impl.js'; +import { AssetImpl } from './asset.impl.js'; import { DataComponentImpl } from './data-component.impl.js'; +import { MitigationImpl } from './mitigation.impl.js'; +import { TacticImpl } from './tactic.impl.js'; export class TechniqueImpl extends AttackBaseImpl { private _subTechniques: TechniqueImpl[] = []; private _tactics: TacticImpl[] = []; private _mitigations: MitigationImpl[] = []; - private _logSources: LogSourceImpl[] = []; private _parentTechnique?: TechniqueImpl; private _relatedTechniques: TechniqueImpl[] = []; private _targetAssets: AssetImpl[] = []; @@ -40,10 +38,6 @@ export class TechniqueImpl extends AttackBaseImpl { this._mitigations.push(mitigation); } - addLogSource(logSource: LogSourceImpl): void { - this._logSources.push(logSource); - } - addRelatedTechnique(technique: TechniqueImpl): void { this._relatedTechniques.push(technique); } @@ -69,10 +63,6 @@ export class TechniqueImpl extends AttackBaseImpl { return this._mitigations; } - getLogSources(): LogSourceImpl[] { - return this._logSources; - } - getParentTechnique(): TechniqueImpl | undefined { return this._parentTechnique; } diff --git a/src/generator/index.ts b/src/generator/index.ts index a1de0b00..f5e9173b 100644 --- a/src/generator/index.ts +++ b/src/generator/index.ts @@ -9,7 +9,6 @@ import { type DetectionStrategy, type Group, type Identity, - type LogSource, type Malware, type MarkingDefinition, type Matrix, @@ -36,7 +35,10 @@ const minimalSdo = { x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }; -const minimalAsset = { +type MinimalSdoKeys = keyof typeof minimalSdo; + +type MinimalAsset = Omit; +const minimalAsset: MinimalAsset = { id: 'x-mitre-asset--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'x-mitre-asset', name: 'Test Asset', @@ -49,7 +51,8 @@ const minimalAsset = { ], }; -const minimalAnalytic = { +type MinimalAnalytic = Omit; +const minimalAnalytic: MinimalAnalytic = { type: 'x-mitre-analytic', id: 'x-mitre-analytic--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'Suspicious PowerShell Activity', @@ -65,8 +68,14 @@ const minimalAnalytic = { description: 'Adversary execution of PowerShell commands with suspicious parameters', x_mitre_log_source_references: [ { - x_mitre_log_source_ref: 'x-mitre-log-source--1a2b3c4d-5e6f-789a-bcde-123456789abc', - permutation_names: ['PowerShell'], + x_mitre_data_component_ref: 'x-mitre-data-component--1a2b3c4d-5e6f-789a-bcde-123456789abc', + name: 'PowerShell', + channel: '1', + }, + { + x_mitre_data_component_ref: 'x-mitre-data-component--1a2b3c4d-5e6f-789a-bcde-123456789abc', + name: 'PowerShell', + channel: '2', }, ], x_mitre_mutable_elements: [ @@ -77,7 +86,8 @@ const minimalAnalytic = { ], }; -const minimalCampaign = { +type MinimalCampaign = Omit; +const minimalCampaign: MinimalCampaign = { type: 'campaign', id: 'campaign--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'Operation Dream Job', @@ -111,7 +121,8 @@ const minimalCampaign = { x_mitre_last_seen_citation: '(Citation: ClearSky Lazarus Aug 2020)', }; -const minimalCollection = { +type MinimalCollection = Omit; +const minimalCollection: MinimalCollection = { type: 'x-mitre-collection', id: 'x-mitre-collection--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'Enterprise ATT&CK', @@ -124,16 +135,28 @@ const minimalCollection = { ], }; -const minimalDataComponent = { +type MinimalDataComponent = Omit; +const minimalDataComponent: MinimalDataComponent = { type: 'x-mitre-data-component', id: 'x-mitre-data-component--1a2b3c4d-5e6f-789a-bcde-123456789abc', description: 'A user requested active directory credentials, such as a ticket or token.', name: 'Network Connection Creation', x_mitre_data_source_ref: 'x-mitre-data-source--c000cd5c-bbb3-4606-af6f-6c6d9de0bbe3', x_mitre_domains: ['enterprise-attack'], + x_mitre_log_sources: [ + { + name: 'WinEventLog:Security', + channel: 'EventCode=4769', + }, + { + name: 'WinEventLog:Security', + channel: 'EventCode=4768', + }, + ], }; -const minimalDataSource = { +type MinimalDataSource = Omit; +const minimalDataSource: MinimalDataSource = { type: 'x-mitre-data-source', id: 'x-mitre-data-source--1a2b3c4d-5e6f-789a-bcde-123456789abc', description: 'Test log source description', @@ -141,7 +164,7 @@ const minimalDataSource = { external_references: [ { source_name: 'mitre-attack', - url: 'https://attack.mitre.org/datasources/DS0014', // TODO change this after website updates to use logsources + url: 'https://attack.mitre.org/datasources/DS0014', external_id: 'DS0014', }, ], @@ -149,7 +172,8 @@ const minimalDataSource = { x_mitre_collection_layers: ['Host'], }; -const minimalDetectionStrategy = { +type MinimalDetectionStrategy = Omit; +const minimalDetectionStrategy: MinimalDetectionStrategy = { type: 'x-mitre-detection-strategy', id: 'x-mitre-detection-strategy--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'PowerShell Command Line Detection', @@ -165,7 +189,8 @@ const minimalDetectionStrategy = { x_mitre_analytic_refs: ['x-mitre-analytic--1a2b3c4d-5e6f-789a-bcde-123456789abc'], }; -const minimalGroup = { +type MinimalGroup = Omit; +const minimalGroup: MinimalGroup = { id: 'intrusion-set--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'intrusion-set', name: 'Test Name', @@ -197,29 +222,8 @@ const minimalIdentity = { x_mitre_attack_spec_version: '3.2.0', }; -const minimalLogSource = { - type: 'x-mitre-log-source', - id: 'x-mitre-log-source--1a2b3c4d-5e6f-789a-bcde-123456789abc', - name: 'Windows Security Event Log', - object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], - external_references: [ - { - source_name: 'mitre-attack', - url: 'https://attack.mitre.org/logsources/LS0001', - external_id: 'LS0001', - }, - ], - x_mitre_domains: ['enterprise-attack'], - x_mitre_log_source_permutations: [ - { - name: 'Security', - channel: 'Security', - data_component_name: 'Security', - }, - ], -}; - -const minimalMalware = { +type MinimalMalware = Omit; +const minimalMalware: MinimalMalware = { type: 'malware', id: 'malware--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'HAMMERTOSS', @@ -249,7 +253,8 @@ const minimalMarkingDefinition = { spec_version: '2.1', }; -const minimalMatrix = { +type MinimalMatrix = Omit; +const minimalMatrix: MinimalMatrix = { id: 'x-mitre-matrix--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'x-mitre-matrix', name: 'Test Matrix', @@ -266,7 +271,8 @@ const minimalMatrix = { tactic_refs: ['x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a'], }; -const minimalMitigation = { +type MinimalMitigation = Omit; +const minimalMitigation: MinimalMitigation = { id: 'course-of-action--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'course-of-action', name: 'Test Mitigation', @@ -295,7 +301,8 @@ const minimalRelationship = { x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }; -const minimalTactic = { +type MinimalTactic = Omit; +const minimalTactic: MinimalTactic = { type: 'x-mitre-tactic', id: 'x-mitre-tactic--1a2b3c4d-5e6f-789a-bcde-123456789abc', name: 'Execution', @@ -311,7 +318,8 @@ const minimalTactic = { x_mitre_domains: ['enterprise-attack'], }; -const minimalTool = { +type MinimalTool = Omit; +const minimalTool: MinimalTool = { id: 'tool--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'tool', name: 'Sliver', @@ -333,7 +341,8 @@ const minimalTool = { x_mitre_domains: ['enterprise-attack'], }; -const minimalTechnique = { +type MinimalTechnique = Omit; +const minimalTechnique: MinimalTechnique = { id: 'attack-pattern--1a2b3c4d-5e6f-789a-bcde-123456789abc', type: 'attack-pattern', name: 'Test Technique', @@ -393,8 +402,6 @@ export function createSyntheticStixObject( return minimalMarkingDefinition as MarkingDefinition; case 'relationship': return minimalRelationship as Relationship; - case 'x-mitre-log-source': - return { ...minimalSdo, ...minimalLogSource } as LogSource; case 'x-mitre-detection-strategy': return { ...minimalSdo, ...minimalDetectionStrategy } as DetectionStrategy; case 'x-mitre-analytic': diff --git a/src/main.ts b/src/main.ts index 092ce58b..0b63856c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,7 +17,6 @@ import { detectionStrategySchema, groupSchema, identitySchema, - logSourceSchema, malwareSchema, markingDefinitionSchema, matrixSchema, @@ -275,9 +274,6 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO case 'relationship': objParseResult = relationshipSchema.safeParse(obj); break; - case 'x-mitre-log-source': - objParseResult = logSourceSchema.safeParse(obj); - break; case 'x-mitre-detection-strategy': objParseResult = detectionStrategySchema.safeParse(obj); break; diff --git a/src/refinements/index.ts b/src/refinements/index.ts index acad7c7a..ffa25ae8 100644 --- a/src/refinements/index.ts +++ b/src/refinements/index.ts @@ -5,8 +5,6 @@ import { type AttackObject, type ExternalReferences, type KillChainPhase, - type Relationship, - type RelationshipType, type StixBundle, type Technique, type XMitreDataSources, @@ -426,47 +424,3 @@ export function createMobileOnlyPropertiesRefinement() { } }; } - -/** - * Creates a refinement function for validating 'found-in' relationships - * - * @returns A refinement function for 'found-in' relationship validation - */ -export function createFoundInRelationshipRefinement() { - return ( - ctx: z.core.ParsePayload< - | Relationship - | { - relationship_type: RelationshipType; - x_mitre_log_source_channel?: z.ZodString; - } - >, - ): void => { - // Must be present if relationship_type is 'found-in' - if (ctx.value.relationship_type === 'found-in' && !ctx.value.x_mitre_log_source_channel) { - ctx.issues.push({ - code: 'custom', - message: "x_mitre_log_source_channel must be defined if relationship_type is 'found-in'", - path: ['x_mitre_log_source_channel'], - input: { - relationship_type: ctx.value.relationship_type, - x_mitre_log_source_channel: ctx.value.x_mitre_log_source_channel, - }, - }); - } - - // Must NOT be present if relationship_type is not 'found-in' - if (ctx.value.relationship_type !== 'found-in' && ctx.value.x_mitre_log_source_channel) { - ctx.issues.push({ - code: 'custom', - message: - "x_mitre_log_source_channel can only be defined if relationship_type is 'found-in'", - path: ['x_mitre_log_source_channel'], - input: { - relationship_type: ctx.value.relationship_type, - x_mitre_log_source_channel: ctx.value.x_mitre_log_source_channel, - }, - }); - } - }; -} diff --git a/src/schemas/common/attack-id.ts b/src/schemas/common/attack-id.ts index 9c7cd929..e3b33447 100644 --- a/src/schemas/common/attack-id.ts +++ b/src/schemas/common/attack-id.ts @@ -58,12 +58,6 @@ const attackIdConfig = { example: 'DS####', stixTypes: ['x-mitre-data-source'] as const, }, - 'log-source': { - pattern: /^LS\d{4}$/, - message: 'Must match ATT&CK Log Source ID format (DS####)', - example: 'LS####', - stixTypes: ['x-mitre-log-source'] as const, - }, campaign: { pattern: /^C\d{4}$/, message: 'Must match ATT&CK Campaign ID format (C####)', @@ -110,7 +104,6 @@ export const stixTypeToAttackIdMapping: Record = { 'x-mitre-data-component': 'DataComponent', 'x-mitre-detection-strategy': 'DetectionStrategy', 'x-mitre-data-source': 'DataSource', - 'x-mitre-log-source': 'LogSource', 'x-mitre-tactic': 'Tactic', 'x-mitre-asset': 'Asset', 'x-mitre-matrix': 'Matrix', @@ -52,7 +51,6 @@ const supportedStixTypes = [ 'x-mitre-tactic', 'x-mitre-asset', 'x-mitre-data-source', - 'x-mitre-log-source', 'x-mitre-matrix', 'x-mitre-collection', 'relationship', diff --git a/src/schemas/sdo/analytic.schema.ts b/src/schemas/sdo/analytic.schema.ts index 268dd925..172d69e9 100644 --- a/src/schemas/sdo/analytic.schema.ts +++ b/src/schemas/sdo/analytic.schema.ts @@ -12,7 +12,7 @@ import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // -// MITRE Log Source Reference +// MITRE Log Source // ///////////////////////////////////// @@ -20,21 +20,25 @@ export const xMitreLogSourcePermutationName = z.string(); export const xMitreLogSourceReferenceSchema = z .object({ - x_mitre_log_source_ref: createStixIdValidator('x-mitre-log-source'), - permutation_names: z.array(z.string()).nonempty().meta({ - description: - 'Must match one of the elements in the ``x_mitre_log_source_permutations`` array', - }), + x_mitre_data_component_ref: createStixIdValidator('x-mitre-data-component'), + name: z + .string() + .nonempty() + .meta({ description: 'The name of the log source that the analytic is referencing' }), + channel: z + .string() + .nonempty() + .meta({ description: 'The channel of the log source that the analytic is referencing' }), }) .meta({ - description: 'A reference to a log source permutation', + description: 'A reference to a log source', }); export type LogSourceReference = z.infer; ///////////////////////////////////// // -// MITRE Log Source References (plural) +// MITRE Log Sources (plural) // ///////////////////////////////////// @@ -42,37 +46,30 @@ export const xMitreLogSourceReferencesSchema = z .array(xMitreLogSourceReferenceSchema) .nonempty() .refine( - // Reject duplicate x_mitre_log_source_ref (cannot reference the same log source twice) - // Reject duplicate permutation name elements for each x_mitre_log_source_ref (cannot reference the same permutation name twice) + // Reject duplicate log source references, delineated by (x_mitre_data_component_ref, name, channel) + // An analytic cannot reference the same log source twice (logSourceReferences) => { const seenRefs = new Set(); for (const logSourceRef of logSourceReferences) { - if (seenRefs.has(logSourceRef.x_mitre_log_source_ref)) { + const key = `${logSourceRef.x_mitre_data_component_ref}|${logSourceRef.name}|${logSourceRef.channel}`; + if (seenRefs.has(key)) { return false; } - seenRefs.add(logSourceRef.x_mitre_log_source_ref); - - const seenPermutationNames = new Set(); - for (const key of logSourceRef.permutation_names) { - if (seenPermutationNames.has(key)) { - return false; - } - seenPermutationNames.add(key); - } + seenRefs.add(key); } return true; }, { message: - 'Duplicate log source permutation found: each (x_mitre_log_source_ref, permutation_names) pair must be unique', + 'Duplicate log source reference found: each (x_mitre_data_component_ref, name, channel) tuple must be unique', path: ['x_mitre_log_source_references'], }, ) .meta({ description: - 'A list of log source STIX IDs, plus the specific permutation names, e.g., sysmon:1 or auditd:SYSCALL.', + "A list of log source references, delineated by the proprietor's STIX ID and the (name, channel) that is being targeted", }); export type LogSourceReferences = z.infer; diff --git a/src/schemas/sdo/data-component.schema.ts b/src/schemas/sdo/data-component.schema.ts index 1ab332bc..b725ca0b 100644 --- a/src/schemas/sdo/data-component.schema.ts +++ b/src/schemas/sdo/data-component.schema.ts @@ -23,6 +23,48 @@ export const xMitreDataSourceRefSchema = createStixIdValidator('x-mitre-data-sou export type XMitreDataSourceRef = z.infer; +///////////////////////////////////// +// +// Log Sources +// (x_mitre_log_sources) +// +///////////////////////////////////// + +export const xMitreLogSourcesSchema = z + .array( + z + .object({ + name: z.string().nonempty(), + channel: z.string().nonempty(), + }) + .strict(), + ) + .nonempty() + .refine( + // Reject duplicate (name, channel) pairs + // Allow same name with different channels + // Allow same channel with different names + (permutations) => { + const seen = new Set(); + + for (const perm of permutations) { + const key = `${perm.name}|${perm.channel}`; + if (seen.has(key)) { + return false; + } + seen.add(key); + } + + return true; + }, + { + message: 'Duplicate log source found: each (name, channel) pair must be unique', + path: ['x_mitre_log_sources'], + }, + ); + +export type XMitreLogSources = z.infer; + ///////////////////////////////////// // // MITRE Data Component @@ -47,7 +89,16 @@ export const dataComponentSchema = attackBaseDomainObjectSchema x_mitre_modified_by_ref: xMitreModifiedByRefSchema, - x_mitre_data_source_ref: xMitreDataSourceRefSchema, + /** + * DEPRECATION NOTICE: + * Data Sources are deprecated and will be removed in a future release + * Consequently, ``x_mitre_data_source_ref`` has been changed to optional + * TODO Remove ``x_mitre_data_source_ref`` in the next major release + */ + x_mitre_data_source_ref: xMitreDataSourceRefSchema.optional(), + + // TODO change to required in spec release 4.x + x_mitre_log_sources: xMitreLogSourcesSchema.optional(), }) .strict(); diff --git a/src/schemas/sdo/index.ts b/src/schemas/sdo/index.ts index a097ea7c..f8a611f5 100644 --- a/src/schemas/sdo/index.ts +++ b/src/schemas/sdo/index.ts @@ -30,8 +30,10 @@ export { export { dataComponentSchema, xMitreDataSourceRefSchema, + xMitreLogSourcesSchema, type DataComponent, type XMitreDataSourceRef, + type XMitreLogSources, } from './data-component.schema.js'; export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js'; @@ -40,13 +42,6 @@ export { groupSchema, type Group } from './group.schema.js'; export { identitySchema, type Identity } from './identity.schema.js'; -export { - logSourceSchema, - xMitreLogSourcePermutationsSchema, - type LogSource, - type XMitreLogSourcePermutations, -} from './log-source.schema.js'; - export { dataSourceSchema, xMitreCollectionLayersSchema, @@ -105,4 +100,10 @@ export { export { toolSchema, type Tool } from './tool.schema.js'; -export { stixBundleSchema, type AttackObject, type StixBundle } from './stix-bundle.schema.js'; +export { + attackObjectsSchema, + stixBundleSchema, + type AttackObject, + type AttackObjects, + type StixBundle, +} from './stix-bundle.schema.js'; diff --git a/src/schemas/sdo/log-source.schema.ts b/src/schemas/sdo/log-source.schema.ts deleted file mode 100644 index 25427d30..00000000 --- a/src/schemas/sdo/log-source.schema.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { z } from 'zod/v4'; -import { - attackBaseDomainObjectSchema, - createAttackExternalReferencesSchema, - createStixIdValidator, - createStixTypeValidator, - xMitreDomainsSchema, - xMitreModifiedByRefSchema, -} from '../common/index.js'; - -///////////////////////////////////// -// -// Log Source Permutations -// (x_mitre_log_source_permutations) -// -///////////////////////////////////// - -export const xMitreLogSourcePermutationsSchema = z - .array( - z.object({ - name: z.string().nonempty(), - channel: z.string().nonempty(), - data_component_name: z.string().nonempty(), - }), - ) - .nonempty() - .refine( - // Reject duplicate (name, channel) pairs - // Allow same name with different channels - // Allow same channel with different names - (permutations) => { - const seen = new Set(); - - for (const perm of permutations) { - const key = `${perm.name}|${perm.channel}`; - if (seen.has(key)) { - return false; - } - seen.add(key); - } - - return true; - }, - { - message: 'Duplicate log source permutation found: each (name, channel) pair must be unique', - path: ['x_mitre_log_source_permutations'], - }, - ); - -export type XMitreLogSourcePermutations = z.infer; - -///////////////////////////////////// -// -// MITRE Log Source SDO -// (x-mitre-log-source) -// -///////////////////////////////////// - -export const logSourceSchema = attackBaseDomainObjectSchema - .extend({ - id: createStixIdValidator('x-mitre-log-source'), - - type: createStixTypeValidator('x-mitre-log-source'), - - // Optional in STIX but required in ATT&CK - external_references: createAttackExternalReferencesSchema('x-mitre-log-source'), - - x_mitre_domains: xMitreDomainsSchema, - - x_mitre_modified_by_ref: xMitreModifiedByRefSchema, - - x_mitre_log_source_permutations: xMitreLogSourcePermutationsSchema, - }) - .required({ - object_marking_refs: true, // Optional in STIX but required in ATT&CK - created_by_ref: true, // Optional in STIX but required in ATT&CK - }) - .strict(); - -export type LogSource = z.infer; diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index be6b8b2e..d917af4c 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -13,7 +13,6 @@ import { type DataSource, dataSourceSchema } from './data-source.schema.js'; import { type DetectionStrategy, detectionStrategySchema } from './detection-strategy.schema.js'; import { type Group, groupSchema } from './group.schema.js'; import { type Identity, identitySchema } from './identity.schema.js'; -import { type LogSource, logSourceSchema } from './log-source.schema.js'; import { type Malware, malwareSchema } from './malware.schema.js'; import { type Matrix, matrixSchema } from './matrix.schema.js'; import { type Mitigation, mitigationSchema } from './mitigation.schema.js'; @@ -27,7 +26,7 @@ export type AttackObject = | Campaign | Collection | DataComponent - | (DataSource | LogSource) + | DataSource | Identity | Matrix | Tool @@ -40,6 +39,129 @@ export type AttackObject = | Relationship | MarkingDefinition; +// Create a schema mapping object to map types to their schemas +const schemaMap = { + get malware() { + return malwareSchema; + }, + get 'x-mitre-asset'() { + return assetSchema; + }, + get campaign() { + return campaignSchema; + }, + get 'x-mitre-collection'() { + return collectionSchema; + }, + get 'x-mitre-data-component'() { + return dataComponentSchema; + }, + get 'x-mitre-data-source'() { + return dataSourceSchema; + }, + get 'x-mitre-detection-strategy'() { + return detectionStrategySchema; + }, + get 'x-mitre-analytic'() { + return analyticSchema; + }, + get identity() { + return identitySchema; + }, + get 'x-mitre-matrix'() { + return matrixSchema; + }, + get tool() { + return toolSchema; + }, + get 'x-mitre-tactic'() { + return tacticSchema; + }, + get 'attack-pattern'() { + return techniqueSchema; + }, + get 'intrusion-set'() { + return groupSchema; + }, + get 'course-of-action'() { + return mitigationSchema; + }, + get relationship() { + return relationshipSchema; + }, + get 'marking-definition'() { + return markingDefinitionSchema; + }, +}; + +// IMPORTANT: Casting the 'attackObjectsSchema' to 'z.ZodTypeAny' is critical. Without it, the TypeScript compiler will get overwhelmed and throw the following: +// 'error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.' + +export const attackObjectsSchema: z.ZodTypeAny = z + .array( + z + .object({ + // Basic structure validation to ensure we have a type field + type: z.string({ + error: (issue) => { + return issue.code === 'invalid_type' + ? "Object 'type' must be a string" + : "The 'type' property is invalid or missing"; + }, + }), + id: z.string({ + error: (issue) => { + return issue.code === 'invalid_type' + ? "Object 'id' must be a string" + : "The 'id' property is invalid or missing"; + }, + }), + }) + .loose() + .check((ctx) => { + const type = ctx.value.type; + + // Uncomment for debugging + // console.log(`Validating object of type: ${type}, ID: ${obj.id}`); + + // Get the schema based on the type + const schema = schemaMap[type as keyof typeof schemaMap]; + + if (!schema) { + ctx.issues.push({ + code: 'custom', + message: `Unknown STIX type: ${type}`, + path: ['type'], + input: ctx.value.type, + }); + return; + } + + // Validate the object against the appropriate schema + // TODO can the following code be cleaned up? + try { + schema.parse(ctx.value); + } catch (error) { + if (error instanceof z.ZodError) { + // Forward all validation issues from the schema + error.issues.forEach((issue) => { + ctx.issues.push(issue); + }); + } else { + // Handle unexpected errors + ctx.issues.push({ + code: 'custom', + message: `Validation error: ${error instanceof Error ? error.message : String(error)}`, + input: ctx.value, // TODO this might be too much information: how can we filter down to just the relevant part? + }); + } + } + }), + ) + .min(1, { message: 'The STIX bundle must contain at least one object.' }); + +export type AttackObjects = z.infer; + ///////////////////////////////////// // // STIX Bundle @@ -50,7 +172,6 @@ export const stixBundleSchema = z .object({ id: createStixIdValidator('bundle'), type: createStixTypeValidator('bundle'), - // objects: attackObjectsSchema, objects: z .array( z.discriminatedUnion('type', [ @@ -63,7 +184,6 @@ export const stixBundleSchema = z detectionStrategySchema, groupSchema, identitySchema, - logSourceSchema, malwareSchema, matrixSchema, mitigationSchema, diff --git a/src/schemas/sro/relationship.schema.ts b/src/schemas/sro/relationship.schema.ts index 012424d1..18153115 100644 --- a/src/schemas/sro/relationship.schema.ts +++ b/src/schemas/sro/relationship.schema.ts @@ -1,4 +1,3 @@ -import { createFoundInRelationshipRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseRelationshipObjectSchema, @@ -28,7 +27,6 @@ const supportedRelationshipTypes = [ 'attributed-to', 'targets', 'revoked-by', - 'found-in', ] as const; export const relationshipTypeSchema = z @@ -47,7 +45,6 @@ export const validRelationshipObjectTypes = [ stixTypeSchema.enum['tool'], stixTypeSchema.enum['x-mitre-data-component'], stixTypeSchema.enum['x-mitre-asset'], - stixTypeSchema.enum['x-mitre-log-source'], ]; type RelationshipMap = Record; @@ -76,7 +73,8 @@ const relationshipMap: RelationshipMap = { }, detects: { source: [ - stixTypeSchema.enum['x-mitre-data-component'], // TODO remove in attack spec 4.0.0 / adm release 5.x + // TODO remove DC ----> Technique in spec release 4.x + stixTypeSchema.enum['x-mitre-data-component'], stixTypeSchema.enum['x-mitre-detection-strategy'], ], target: [stixTypeSchema.enum['attack-pattern']], @@ -93,10 +91,6 @@ const relationshipMap: RelationshipMap = { source: validRelationshipObjectTypes, target: validRelationshipObjectTypes, }, - 'found-in': { - source: [stixTypeSchema.enum['x-mitre-data-component']], - target: [stixTypeSchema.enum['x-mitre-log-source']], - }, } as const; // Invalid "uses" combinations @@ -300,17 +294,25 @@ export const relationshipSchema = attackBaseRelationshipObjectSchema target_ref: stixIdentifierSchema.meta({ description: 'The ID of the target (to) object.' }), x_mitre_modified_by_ref: xMitreModifiedByRefSchema, - - x_mitre_log_source_channel: z.string().nonempty().optional(), }) .omit({ name: true, x_mitre_version: true, }) .strict() - .check((ctx) => { - createRelationshipValidationRefinement()(ctx); - createFoundInRelationshipRefinement()(ctx); + .transform((data) => { + // Check for deprecated pattern + const [sourceType] = data.source_ref.split('--') as [StixType]; + if ( + sourceType === 'x-mitre-data-component' && + data.relationship_type === 'detects' && + data.target_ref.startsWith('attack-pattern--') + ) { + console.warn( + 'DEPRECATION WARNING: x-mitre-data-component -> detects -> attack-pattern relationships are deprecated', + ); + } + return data; }); export type Relationship = z.infer; diff --git a/test/objects/analytic.test.ts b/test/objects/analytic.test.ts index b3948a53..ac81620a 100644 --- a/test/objects/analytic.test.ts +++ b/test/objects/analytic.test.ts @@ -23,12 +23,14 @@ describe('analyticSchema', () => { x_mitre_platforms: ['Windows'], x_mitre_log_source_references: [ { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell'], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'PowerShell', + channel: '1' }, { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['Security', 'System'], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'Security', + channel: '2' }, ], x_mitre_mutable_elements: [ @@ -50,16 +52,19 @@ describe('analyticSchema', () => { ...minimalAnalytic, x_mitre_log_source_references: [ { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell'], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'WinEventLog:Security', + channel: 'EventCode=4769' }, { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['Security', 'Application'], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'WinEventLog:Security', + channel: 'EventCode=4624' }, { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['Sysmon'], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'foo', + channel: 'bar' }, ], x_mitre_mutable_elements: [ @@ -98,12 +103,12 @@ describe('analyticSchema', () => { describe('id', () => { testField('id', 'invalid-id'); - testField('id', `x-mitre-log-source--${uuidv4()}`); // Wrong prefix + testField('id', `x-mitre-data-component--${uuidv4()}`); // Wrong prefix }); describe('type', () => { testField('type', 'invalid-type'); - testField('type', 'x-mitre-log-source'); // Wrong type + testField('type', 'x-mitre-data-component'); // Wrong type }); describe('created_by_ref', () => { @@ -134,11 +139,15 @@ describe('analyticSchema', () => { expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject log source references with invalid x_mitre_log_source_ref format', () => { + it('should reject log source references with invalid x_mitre_data_component_ref format', () => { const invalidObject = { ...minimalAnalytic, x_mitre_log_source_references: [ - { x_mitre_log_source_ref: 'invalid-log-source-ref', permutation_names: ['PowerShell'] }, + { + x_mitre_data_component_ref: 'invalid-data-component-ref', + name: 'PowerShell', + channel: '1' + }, ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); @@ -148,81 +157,97 @@ describe('analyticSchema', () => { const invalidObject = { ...minimalAnalytic, x_mitre_log_source_references: [ - { x_mitre_log_source_ref: `x-mitre-analytic--${uuidv4()}`, permutation_names: ['PowerShell'] }, + { + x_mitre_data_component_ref: `x-mitre-analytic--${uuidv4()}`, + name: 'PowerShell', + channel: '1' + }, ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject log source references with empty permutation_names array', () => { + it('should reject log source references with empty name', () => { const invalidObject = { ...minimalAnalytic, x_mitre_log_source_references: [ - { x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, permutation_names: [] }, + { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: '', + channel: '1' + }, ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject log source references missing x_mitre_log_source_ref field', () => { + it('should reject log source references with empty channel', () => { const invalidObject = { ...minimalAnalytic, - x_mitre_log_source_references: [{ permutation_names: ['PowerShell'] }], + x_mitre_log_source_references: [ + { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'foobar', + channel: '' + }, + ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject log source references missing permutation_names field', () => { + it('should reject log source references missing x_mitre_data_component_ref field', () => { const invalidObject = { ...minimalAnalytic, x_mitre_log_source_references: [ - { x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}` }, + { + name: 'PowerShell', + channel: '1' + } ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject identical log source references', () => { - const duplicateRef: LogSourceReference = { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell'], - }; + it('should reject log source references missing name field', () => { const invalidObject = { ...minimalAnalytic, - x_mitre_log_source_references: [duplicateRef, duplicateRef], + x_mitre_log_source_references: [ + { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + channel: '1' + }, + ], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should reject duplicate log source references with duplicate x_mitre_log_source_ref', () => { - const duplicateId = `x-mitre-log-source--${uuidv4()}`; + it('should reject identical log source references', () => { + const duplicateRef: LogSourceReference = { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'PowerShell', + channel: '1' + }; const invalidObject = { ...minimalAnalytic, - x_mitre_log_source_references: [ - { - x_mitre_log_source_ref: duplicateId, - permutation_names: ['Foo'], - }, - { - x_mitre_log_source_ref: duplicateId, - permutation_names: ['Bar'], - }, - ], + x_mitre_log_source_references: [duplicateRef, duplicateRef], }; expect(() => analyticSchema.parse(invalidObject)).toThrow(); }); - it('should accept log source references with overlapping permutation_names', () => { + it('should accept log source references with overlapping x_mitre_data_component_ref + name (different channel only)', () => { + const dupeId = `x-mitre-data-component--${uuidv4()}`; const invalidObject = { ...minimalAnalytic, x_mitre_log_source_references: [ { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell'], + x_mitre_data_component_ref: dupeId, + name: 'PowerShell', + channel: '1' // <-- different }, { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell'], + x_mitre_data_component_ref: dupeId, + name: 'PowerShell', + channel: '2' // <-- different }, ], }; @@ -364,19 +389,6 @@ describe('analyticSchema', () => { expect(() => analyticSchema.parse(analyticWithSpecialChars)).not.toThrow(); }); - it('should handle multiple permutation_names in log source references', () => { - const analyticWithMultipleKeys: Analytic = { - ...minimalAnalytic, - x_mitre_log_source_references: [ - { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: ['PowerShell', 'Security', 'Application', 'System'], - }, - ], - }; - expect(() => analyticSchema.parse(analyticWithMultipleKeys)).not.toThrow(); - }); - it('should handle very long field names and descriptions in mutable elements', () => { const longString = 'A'.repeat(500); const analyticWithLongMutableElements: Analytic = { @@ -409,12 +421,19 @@ describe('analyticSchema', () => { ...minimalAnalytic, x_mitre_log_source_references: [ { - x_mitre_log_source_ref: `x-mitre-log-source--${uuidv4()}`, - permutation_names: [ - 'sysmon:1', - 'auditd:SYSCALL', - 'Security/Microsoft-Windows-Security-Auditing', - ], + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'sysmon:1', + channel: '1' + }, + { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'auditd:SYSCALL', + channel: '1' + }, + { + x_mitre_data_component_ref: `x-mitre-data-component--${uuidv4()}`, + name: 'Security/Microsoft-Windows-Security-Auditing', + channel: '1' }, ], }; diff --git a/test/objects/data-component.test.ts b/test/objects/data-component.test.ts index 593614ab..84f6f305 100644 --- a/test/objects/data-component.test.ts +++ b/test/objects/data-component.test.ts @@ -65,8 +65,61 @@ describe('dataComponentSchema', () => { testField('x_mitre_modified_by_ref', 'invalid-modified-by-ref'); }); - describe('x_mitre_data_source_ref', () => { - testField('x_mitre_data_source_ref', 'invalid-data-source-ref'); + describe('x_mitre_log_sources', () => { + it('should reject empty array', () => { + const invalidObject = { ...minimalDataComponent, x_mitre_log_sources: [] }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject log sources with empty name', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: [{ name: '', channel: 'Security' }], + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject log sources with empty channel', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: [{ name: 'Security', channel: '' }], + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject log sources with erroneous keys', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { name: 'Security', channel: 'Security', foobar: '' }, // foobar not allowed + ], + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject log sources missing name', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: [{ channel: 'Security' }], + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject log sources missing channel', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: [{ name: 'Security' }], + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); + + it('should reject non-array value', () => { + const invalidObject = { + ...minimalDataComponent, + x_mitre_log_sources: 'not-an-array', + }; + expect(() => dataComponentSchema.parse(invalidObject)).toThrow(); + }); }); // Optional Fields Testing @@ -83,5 +136,95 @@ describe('dataComponentSchema', () => { } as DataComponent; expect(() => dataComponentSchema.parse(invalidDataComponent)).toThrow(); }); + + it('should enforce strict mode', () => { + const invalidDataComponent = { + ...minimalDataComponent, + foo: 'bar', // Not a valid field + }; + expect(() => dataComponentSchema.parse(invalidDataComponent)).toThrow(); + }); + }); + + describe('Edge Cases and Special Scenarios', () => { + it('should reject log sources with identical (name, channel) pairs', () => { + const dataComponentWithIdenticalLogSources: DataComponent = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { + name: 'Security', + channel: 'Security', + }, + { + name: 'Security', + channel: 'Security', + }, + ], + }; + expect(() => dataComponentSchema.parse(dataComponentWithIdenticalLogSources)).toThrow( + /Duplicate log source found/, + ); + }); + + it('should allow log sources with same name but different channel', () => { + const dataComponentWithLogSourceWithSameName: DataComponent = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { + name: 'Security', + channel: 'Security', + }, + { + name: 'Security', + channel: 'Application', + }, + ], + }; + expect(() => dataComponentSchema.parse(dataComponentWithLogSourceWithSameName)).not.toThrow(); + }); + + it('should allow log sources with same channel but different name', () => { + const dataComponentWithLogSourcesWithSameChannel: DataComponent = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { + name: 'Security', + channel: 'EventLog', + }, + { + name: 'Application', + channel: 'EventLog', + }, + ], + }; + expect(() => dataComponentSchema.parse(dataComponentWithLogSourcesWithSameChannel)).not.toThrow(); + }); + + it('should handle very long log source names and channels', () => { + const longString = 'A'.repeat(1000); + const logSourceWithLongStrings: DataComponent = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { + name: longString, + channel: longString, + }, + ], + }; + expect(() => dataComponentSchema.parse(logSourceWithLongStrings)).not.toThrow(); + }); + + it('should handle special characters in log source fields', () => { + const logSourceWithSpecialChars: DataComponent = { + ...minimalDataComponent, + x_mitre_log_sources: [ + { + name: 'Security/Application-Logs_2024', + channel: 'Microsoft-Windows-Security-Auditing/Operational', + }, + ], + }; + expect(() => dataComponentSchema.parse(logSourceWithSpecialChars)).not.toThrow(); + }); }); }); diff --git a/test/objects/log-source.test.ts b/test/objects/log-source.test.ts deleted file mode 100644 index 401c3bd6..00000000 --- a/test/objects/log-source.test.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; -import { type ExternalReferences } from '../../src/schemas/common/index'; -import { type LogSource, logSourceSchema } from '../../src/schemas/sdo/log-source.schema'; - -describe('logSourceSchema', () => { - const minimalLogSource = createSyntheticStixObject('x-mitre-log-source'); - - describe('Valid Inputs', () => { - it('should accept minimal valid object (only required fields)', () => { - expect(() => logSourceSchema.parse(minimalLogSource)).not.toThrow(); - }); - - it('should accept fully populated valid object (required + optional ATT&CK fields)', () => { - const fullLogSource: LogSource = { - ...minimalLogSource, - x_mitre_deprecated: false, - x_mitre_log_source_permutations: [ - { - name: 'Security', - channel: 'Security', - data_component_name: 'Process Creation', - }, - { - name: 'System', - channel: 'System', - data_component_name: 'Process Creation', - }, - ], - }; - expect(() => logSourceSchema.parse(fullLogSource)).not.toThrow(); - }); - - it('should accept multiple log source permutations', () => { - const multiPermutationLogSource: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: 'Application', - channel: 'Application', - data_component_name: 'Process Creation', - }, - { - name: 'Security', - channel: 'Security', - data_component_name: 'Process Creation', - }, - { - name: 'System', - channel: 'System', - data_component_name: 'Process Creation', - }, - ], - }; - expect(() => logSourceSchema.parse(multiPermutationLogSource)).not.toThrow(); - }); - }); - - describe('Field-Specific Tests', () => { - const testField = (fieldName: keyof LogSource, invalidValue: any, isRequired = true) => { - it(`should reject invalid values for ${fieldName}`, () => { - const invalidObject = { ...minimalLogSource, [fieldName]: invalidValue }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - if (isRequired) { - it(`should reject omission of ${fieldName}`, () => { - const { [fieldName]: omitted, ...objectWithoutField } = minimalLogSource; - expect(() => logSourceSchema.parse(objectWithoutField)).toThrow(); - }); - } - }; - - describe('id', () => { - testField('id', 'invalid-id'); - testField('id', `x-mitre-data-source--${uuidv4()}`); // Wrong prefix - }); - - describe('type', () => { - testField('type', 'invalid-type'); - testField('type', 'x-mitre-data-source'); // Wrong type - }); - - describe('created_by_ref', () => { - testField('created_by_ref', 'invalid-created-by-ref'); - }); - - describe('external_references', () => { - testField('external_references', 'not-an-array' as unknown as ExternalReferences); - testField('external_references', []); // Empty array should fail - }); - - describe('object_marking_refs', () => { - testField('object_marking_refs', ['invalid-object-marking-refs']); - }); - - describe('x_mitre_domains', () => { - testField('x_mitre_domains', ['invalid-mitre-domains']); - }); - - describe('x_mitre_modified_by_ref', () => { - testField('x_mitre_modified_by_ref', 'invalid-modified-by-ref'); - }); - - describe('x_mitre_log_source_permutations', () => { - it('should reject empty array', () => { - const invalidObject = { ...minimalLogSource, x_mitre_log_source_permutations: [] }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations with empty name', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { name: '', channel: 'Security', data_component_name: 'Security' }, - ], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations with empty channel', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { name: 'Security', channel: '', data_component_name: 'Security' }, - ], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations with empty data component name', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { name: 'Security', channel: 'Security', data_component_name: '' }, - ], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations missing name', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { channel: 'Security', data_component_name: 'Security' }, - ], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations missing channel', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [{ name: 'Security', data_component_name: 'Security' }], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject permutations missing data component name', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: [{ name: 'Security', channel: 'Security' }], - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - - it('should reject non-array value', () => { - const invalidObject = { - ...minimalLogSource, - x_mitre_log_source_permutations: 'not-an-array', - }; - expect(() => logSourceSchema.parse(invalidObject)).toThrow(); - }); - }); - - // Optional Fields Testing - describe('x_mitre_deprecated', () => { - testField('x_mitre_deprecated', 'not-a-boolean', false); - }); - }); - - describe('Schema Refinements', () => { - describe('External References Validation', () => { - it('should reject when ATT&CK ID is missing', () => { - const invalidLogSource = { - ...minimalLogSource, - external_references: [{ source_name: 'mitre-attack' }], - }; - expect(() => logSourceSchema.parse(invalidLogSource)).toThrow(/ATT&CK ID must be defined/); - }); - - it('should reject invalid ATT&CK ID format', () => { - const invalidLogSource = { - ...minimalLogSource, - external_references: [{ source_name: 'mitre-attack', external_id: 'LS123' }], - }; - expect(() => logSourceSchema.parse(invalidLogSource)).toThrow( - `The first external_reference must match the ATT&CK ID format LS####.`, - ); - }); - - it('should reject ATT&CK ID with wrong prefix', () => { - const invalidLogSource = { - ...minimalLogSource, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS0001' }], - }; - expect(() => logSourceSchema.parse(invalidLogSource)).toThrow( - `The first external_reference must match the ATT&CK ID format LS####.`, - ); - }); - }); - }); - - describe('Schema-Level Tests', () => { - it('should reject unknown properties', () => { - const invalidLogSource = { - ...minimalLogSource, - unknown_property: true, - } as LogSource; - expect(() => logSourceSchema.parse(invalidLogSource)).toThrow(); - }); - - it('should enforce strict mode', () => { - const invalidLogSource = { - ...minimalLogSource, - foo: 'bar', // Not a valid field - }; - expect(() => logSourceSchema.parse(invalidLogSource)).toThrow(); - }); - }); - - describe('Edge Cases and Special Scenarios', () => { - it('should reject log source permutations with identical (name, channel) pairs', () => { - const logSourceWithIdenticalPermutations: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: 'Security', - channel: 'Security', - data_component_name: 'Security', - }, - { - name: 'Security', - channel: 'Security', - data_component_name: 'Security', - }, - ], - }; - expect(() => logSourceSchema.parse(logSourceWithIdenticalPermutations)).toThrow( - /Duplicate log source permutation found/, - ); - }); - - it('should allow permutations with same name but different channel', () => { - const logSourceWithSameName: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: 'Security', - channel: 'Security', - data_component_name: 'Security', - }, - { - name: 'Security', - channel: 'Application', - data_component_name: 'Security', - }, - ], - }; - expect(() => logSourceSchema.parse(logSourceWithSameName)).not.toThrow(); - }); - - it('should allow permutations with same channel but different name', () => { - const logSourceWithSameChannel: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: 'Security', - channel: 'EventLog', - data_component_name: 'Security', - }, - { - name: 'Application', - channel: 'EventLog', - data_component_name: 'Security', - }, - ], - }; - expect(() => logSourceSchema.parse(logSourceWithSameChannel)).not.toThrow(); - }); - - it('should handle very long permutation names, channels, and data component names', () => { - const longString = 'A'.repeat(1000); - const logSourceWithLongStrings: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: longString, - channel: longString, - data_component_name: longString, - }, - ], - }; - expect(() => logSourceSchema.parse(logSourceWithLongStrings)).not.toThrow(); - }); - - it('should handle special characters in permutation fields', () => { - const logSourceWithSpecialChars: LogSource = { - ...minimalLogSource, - x_mitre_log_source_permutations: [ - { - name: 'Security/Application-Logs_2024', - channel: 'Microsoft-Windows-Security-Auditing/Operational', - data_component_name: 'Security', - }, - ], - }; - expect(() => logSourceSchema.parse(logSourceWithSpecialChars)).not.toThrow(); - }); - }); -}); From 49c296e3941cb0a8d5b2582b10cb95f857d35a99 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:00:56 -0400 Subject: [PATCH 38/39] fix: restore missing imports --- src/schemas/sdo/stix-bundle.schema.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index d917af4c..567db6c3 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -2,8 +2,11 @@ import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { createStixIdValidator } from '../common/stix-identifier.js'; import { createStixTypeValidator } from '../common/stix-type.js'; -import { type MarkingDefinition } from '../smo/marking-definition.schema.js'; -import { type Relationship } from '../sro/relationship.schema.js'; +import { + markingDefinitionSchema, + type MarkingDefinition, +} from '../smo/marking-definition.schema.js'; +import { relationshipSchema, type Relationship } from '../sro/relationship.schema.js'; import { type Analytic, analyticSchema } from './analytic.schema.js'; import { type Asset, assetSchema } from './asset.schema.js'; import { type Campaign, campaignSchema } from './campaign.schema.js'; From 5e9151b8386edc68d116e3a9b6e99779bcd496ad Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:24:41 -0400 Subject: [PATCH 39/39] fix(relationship.schema): restore transform to flag deprecation warning --- src/schemas/sro/relationship.schema.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/schemas/sro/relationship.schema.ts b/src/schemas/sro/relationship.schema.ts index a1ab4216..4c59035c 100644 --- a/src/schemas/sro/relationship.schema.ts +++ b/src/schemas/sro/relationship.schema.ts @@ -302,6 +302,20 @@ export const relationshipSchema = attackBaseRelationshipObjectSchema .strict() .check((ctx) => { createRelationshipValidationRefinement()(ctx); + }) + .transform((data) => { + // Check for deprecated pattern + const [sourceType] = data.source_ref.split('--') as [StixType]; + if ( + sourceType === 'x-mitre-data-component' && + data.relationship_type === 'detects' && + data.target_ref.startsWith('attack-pattern--') + ) { + console.warn( + 'DEPRECATION WARNING: x-mitre-data-component -> detects -> attack-pattern relationships are deprecated', + ); + } + return data; }); export type Relationship = z.infer;