From 524cdc38e6aff7d246352cdd039ff6bdbc80b16a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:59:47 -0400 Subject: [PATCH 01/13] feat: implement Validated base class and refactor TechniqueImpl - Add Validated base class with Zod schema integration - Refactor TechniqueImpl to use new architecture pattern - Replace interface declaration merging with schema-based approach - Add comprehensive relationship management with private fields - Include domain-specific utility methods (getAttackId, supportsPlatform, etc.) - Add technique-impl.example.ts demonstrating usage patterns - Remove TypeScript linting suppressions from implementation This proof of concept establishes the foundation for overhauling all *Impl classes to eliminate declaration merging workarounds and improve type safety. --- examples/sdo/technique-impl.example.ts | 129 +++++++++++++++++ src/classes/common/index.ts | 1 + src/classes/common/validated.ts | 136 ++++++++++++++++++ src/classes/sdo/technique.impl.ts | 188 +++++++++++++++++-------- 4 files changed, 399 insertions(+), 55 deletions(-) create mode 100644 examples/sdo/technique-impl.example.ts create mode 100644 src/classes/common/validated.ts diff --git a/examples/sdo/technique-impl.example.ts b/examples/sdo/technique-impl.example.ts new file mode 100644 index 00000000..871c582e --- /dev/null +++ b/examples/sdo/technique-impl.example.ts @@ -0,0 +1,129 @@ +import { z } from "zod/v4"; +import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; +import { type TechniqueCls, TechniqueImpl } from "../../src/classes/sdo/technique.impl.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.safeParse(validEnterpriseTechnique).success}`) + +let technique: TechniqueCls +try { + const invalidTechnique = { ...validEnterpriseTechnique, id: 'foobar' } + technique = new TechniqueImpl(invalidTechnique); +} catch (err) { + console.error('Could not initialize class instance due to Zod error:') + console.error(z.prettifyError(err)); + // Could not initialize class instance due to Zod error: + // ✖ Invalid STIX Identifier: must comply with format 'type--UUIDv4' + // → at id + // ✖ Invalid STIX Identifier for STIX object: contains invalid STIX type 'foobar' + // → at id + // ✖ Invalid STIX Identifier for STIX object: contains invalid UUIDv4 format + // → at id + // ✖ Invalid STIX Identifier: must start with 'attack-pattern--' + // → at id + process.exit() +} + +console.log(technique.id); +// attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842 + +console.log(typeof technique.id); +// string + +console.log(technique.spec_version); +// 2.1 + +console.log(technique.external_references); +// [ +// { +// source_name: 'mitre-attack', +// url: 'https://attack.mitre.org/techniques/T1001', +// external_id: 'T1001' +// } +// ] + +console.log(technique.getAttackId()); +// T1001 + +console.log(technique.getDisplayName()); +// T1001: Data Obfuscation + +console.log(technique.equals(new TechniqueImpl(validEnterpriseTechnique))) +// true + +console.log(Object.keys(technique)) +/** + [ + 'id', + 'type', + 'spec_version', + 'created', + 'modified', + 'created_by_ref', + 'revoked', + 'external_references', + 'object_marking_refs', + 'name', + 'x_mitre_attack_spec_version', + 'x_mitre_version', + 'x_mitre_deprecated', + 'kill_chain_phases', + 'description', + 'x_mitre_platforms', + 'x_mitre_is_subtechnique', + 'x_mitre_domains', + 'x_mitre_modified_by_ref', + '_subTechniques', + '_tactics', + '_mitigations', + '_logSources', + '_relatedTechniques', + '_targetAssets', + '_detectingDataComponents' + ] + */ + diff --git a/src/classes/common/index.ts b/src/classes/common/index.ts index 574d6ea6..5f31fd8d 100644 --- a/src/classes/common/index.ts +++ b/src/classes/common/index.ts @@ -1 +1,2 @@ +export * from './validated.js'; export * from './attack-object.impl.js'; diff --git a/src/classes/common/validated.ts b/src/classes/common/validated.ts new file mode 100644 index 00000000..476b4be1 --- /dev/null +++ b/src/classes/common/validated.ts @@ -0,0 +1,136 @@ +import { z } from 'zod/v4'; + +type IsPrimitive = T extends object ? false : true; + +export type ValidatedConstructor, WrapValue extends boolean> = { + new ( + value: z.input, + ): Readonly> } : z.infer>; + schema: Schema; + z: >( + this: T, + ) => z.ZodType, z.input>; +}; + +export type ValidatedMutableConstructor< + Schema extends z.ZodType, + WrapValue extends boolean, +> = { + new ( + value: z.input, + ): WrapValue extends true ? { value: z.infer } : z.infer; + schema: Schema; + z: >( + this: T, + ) => z.ZodType, z.input>; +}; + +export const Validated = < + Schema extends z.ZodType, + Options extends { wrapValue: true } | null = null, +>( + schema: Schema, + options?: Options, +) => { + const ctor = function Validated(this: Record, value: z.input) { + const validatedValue = schema.parse(value); + const wrapValue = !isObject(validatedValue) || options?.wrapValue; + const _this = wrapValue ? { value: validatedValue } : validatedValue; + return Object.create(this, Object.getOwnPropertyDescriptors(_this)); + } as unknown as ValidatedConstructor< + Schema, + Options extends { wrapValue: true } ? true : IsPrimitive> + >; + ctor.schema = schema; + ctor.z = function (this: T) { + return z.any().transform((data, ctx) => { + try { + return new this(data) as InstanceType; + } catch (error) { + if (error instanceof z.ZodError) { + for (const issue of error.issues) { + ctx.addIssue(issue); + } + return z.NEVER; + } + throw error; + } + }); + }; + return ctor; +}; + +export const ValidatedMutable = < + Schema extends z.ZodType, + Options extends { wrapValue: true } | null = null, +>( + schema: Schema, + options?: Options, +) => { + const makeValidatedValueProxy = (initialInput: unknown) => { + const inputObject: Record = {}; + if (isObject(initialInput)) { + Object.assign(inputObject, initialInput); + } + return (validatedValue: object) => { + return new Proxy(validatedValue, { + set(object, propertyName, newValue) { + inputObject[propertyName] = newValue; + const validatedNewValue = schema.parse(inputObject) as Record; + return Reflect.set(object, propertyName, validatedNewValue[propertyName]); + }, + }); + }; + }; + const ctor = function ValidatedMutable( + this: Record, + value: z.input, + ) { + const validatedValue = schema.parse(value); + if (!isObject(validatedValue) || options?.wrapValue) { + const validatedValueProxy = isObject(validatedValue) + ? makeValidatedValueProxy(value)(validatedValue) + : validatedValue; + const _this = { value: validatedValueProxy }; + return new Proxy(Object.create(this, Object.getOwnPropertyDescriptors(_this)), { + set(object, propertyName, newValue) { + if (propertyName !== 'value') { + return Reflect.set(object, propertyName, newValue); + } + const validatedNewValue = schema.parse(newValue); + const validatedNewValueProxy = isObject(validatedNewValue) + ? makeValidatedValueProxy(newValue)(validatedNewValue) + : validatedNewValue; + return Reflect.set(object, 'value', validatedNewValueProxy); + }, + }); + } + const _this = validatedValue; + return makeValidatedValueProxy(value)( + Object.create(this, Object.getOwnPropertyDescriptors(_this)), + ); + } as unknown as ValidatedMutableConstructor< + Schema, + Options extends { wrapValue: true } ? true : IsPrimitive> + >; + ctor.schema = schema; + ctor.z = function (this: T) { + return z.any().transform((data, ctx) => { + try { + return new this(data) as InstanceType; + } catch (error) { + if (error instanceof z.ZodError) { + for (const issue of error.issues) { + ctx.addIssue(issue); + } + return z.NEVER; + } + throw error; + } + }); + }; + return ctor; +}; + +const isObject = (value: unknown): value is object => + value !== null && (typeof value === 'object' || typeof value === 'function'); diff --git a/src/classes/sdo/technique.impl.ts b/src/classes/sdo/technique.impl.ts index 41c1b8ab..eb1bf645 100644 --- a/src/classes/sdo/technique.impl.ts +++ b/src/classes/sdo/technique.impl.ts @@ -1,97 +1,175 @@ -/* 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 { DataComponentImpl } from './data-component.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[] = []; - private _detectingDataComponents: DataComponentImpl[] = []; - - constructor(readonly technique: Technique) { - super(); - // Assign properties from the Technique object to this instance - Object.assign(this, technique); - } - +import { Validated } from '../common/index.js'; +import { techniqueSchema } from '@/schemas/sdo/technique.schema.js'; +import type { AttackObject, Technique } from '@/schemas/sdo/index.js'; +import type { TacticImpl } from './tactic.impl.js'; +import type { MitigationImpl } from './mitigation.impl.js'; +import type { LogSourceImpl } from './log-source.impl.js'; +import type { AssetImpl } from './asset.impl.js'; +import type { DataComponentImpl } from './data-component.impl.js'; +import type { StixModifiedTimestamp, XMitrePlatform } from '@/schemas/common/index.js'; + +export class TechniqueImpl extends Validated(techniqueSchema) { + // Relationship tracking (mutable, not part of the JSON data) + #subTechniques: TechniqueImpl[] = []; + #tactics: TacticImpl[] = []; + #mitigations: MitigationImpl[] = []; + #logSources: LogSourceImpl[] = []; + #parentTechnique?: TechniqueImpl; + #relatedTechniques: TechniqueImpl[] = []; + #targetAssets: AssetImpl[] = []; + #detectingDataComponents: DataComponentImpl[] = []; + #revokedBy?: AttackObject; + + // Relationship management methods setParent(parent: TechniqueImpl): void { - this._parentTechnique = parent; + this.#parentTechnique = parent; } addSubTechnique(subTechnique: TechniqueImpl): void { - this._subTechniques.push(subTechnique); + if (!this.#subTechniques.includes(subTechnique)) { + this.#subTechniques.push(subTechnique); + } } addTactic(tactic: TacticImpl): void { - this._tactics.push(tactic); + if (!this.#tactics.includes(tactic)) { + this.#tactics.push(tactic); + } } addMitigation(mitigation: MitigationImpl): void { - this._mitigations.push(mitigation); + if (!this.#mitigations.includes(mitigation)) { + this.#mitigations.push(mitigation); + } } addLogSource(logSource: LogSourceImpl): void { - this._logSources.push(logSource); + if (!this.#logSources.includes(logSource)) { + this.#logSources.push(logSource); + } } addRelatedTechnique(technique: TechniqueImpl): void { - this._relatedTechniques.push(technique); + if (!this.#relatedTechniques.includes(technique)) { + this.#relatedTechniques.push(technique); + } } addTargetAsset(asset: AssetImpl): void { - this._targetAssets.push(asset); + if (!this.#targetAssets.includes(asset)) { + this.#targetAssets.push(asset); + } } addDetectingDataComponent(dataComponent: DataComponentImpl): void { - this._detectingDataComponents.push(dataComponent); + if (!this.#detectingDataComponents.includes(dataComponent)) { + this.#detectingDataComponents.push(dataComponent); + } } - // Getters - public getSubTechniques(): TechniqueImpl[] { - return this._subTechniques; + // Getters for relationships + getSubTechniques(): readonly TechniqueImpl[] { + return [...this.#subTechniques]; } - getTactics(): TacticImpl[] { - return this._tactics; + getTactics(): readonly TacticImpl[] { + return [...this.#tactics]; } - getMitigations(): MitigationImpl[] { - return this._mitigations; + getMitigations(): readonly MitigationImpl[] { + return [...this.#mitigations]; } - getLogSources(): LogSourceImpl[] { - return this._logSources; + getLogSources(): readonly LogSourceImpl[] { + return [...this.#logSources]; } getParentTechnique(): TechniqueImpl | undefined { - return this._parentTechnique; + return this.#parentTechnique; } - getRelatedTechniques(): TechniqueImpl[] { - return this._relatedTechniques; + getRelatedTechniques(): readonly TechniqueImpl[] { + return [...this.#relatedTechniques]; } - getTargetAssets(): AssetImpl[] { - return this._targetAssets; + getTargetAssets(): readonly AssetImpl[] { + return [...this.#targetAssets]; } - getDetectingDataComponents(): DataComponentImpl[] { - return this._detectingDataComponents; + getDetectingDataComponents(): readonly DataComponentImpl[] { + return [...this.#detectingDataComponents]; + } + + // Common functionality + getRevokedBy(): AttackObject | undefined { + return this.#revokedBy; + } + + setRevokedBy(obj: AttackObject | undefined) { + this.#revokedBy = obj; + } + + isDeprecated(): boolean { + return this.x_mitre_deprecated ?? false; + } + + isRevoked(): boolean { + return this.revoked ?? false; + } + + isSubTechnique(): boolean { + return this.x_mitre_is_subtechnique ?? false; + } + + getAttackId(): string | undefined { + return this.external_references?.[0]?.external_id; } -} -// Suppress the lint error for the empty interface -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface TechniqueImpl extends Technique {} + getDisplayName(): string { + const attackId = this.getAttackId(); + return attackId ? `${attackId}: ${this.name}` : this.name; + } + + // Get tactics from kill chain phases + getTacticNames(): string[] { + return this.kill_chain_phases?.map((phase) => phase.phase_name) ?? []; + } + + // Get platforms as a comma-separated string + getPlatformsString(): string { + return this.x_mitre_platforms?.join(', ') ?? ''; + } + + // Check if technique applies to a specific platform + supportsPlatform(platform: string): boolean { + return this.x_mitre_platforms?.includes(platform as XMitrePlatform) ?? false; + } + + // Create a new instance with updated fields + with(updates: Partial): TechniqueImpl { + const newData = { ...this, ...updates }; + return new TechniqueImpl(newData); + } + + // Create a new instance with updated modified timestamp + touch(): TechniqueImpl { + return this.with({ + modified: new Date().toISOString() as StixModifiedTimestamp, + }); + } + + // Equality check + equals(other: TechniqueImpl): boolean { + return this.id === other.id && this.modified === other.modified; + } + + // Check if this version is newer than another + isNewerThan(other: TechniqueImpl): boolean { + if (this.id !== other.id) { + throw new Error('Cannot compare different techniques'); + } + return new Date(this.modified) > new Date(other.modified); + } +} -/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */ +export type TechniqueCls = Technique & typeof TechniqueImpl; From 8f45e1939c16e3f8319e455d8ff6a1bf966c4f20 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:36:46 -0400 Subject: [PATCH 02/13] refactor: rename src/classes to src/api --- docs/USAGE.md | 2 +- examples/sdo/technique-impl.example.ts | 2 +- src/{classes => api}/attack-data-model.ts | 0 .../common/attack-object.impl.ts | 0 src/{classes => api}/common/index.ts | 0 src/{classes => api}/common/validated.ts | 0 src/{classes => api}/index.ts | 0 src/{classes => api}/sdo/analytic.impl.ts | 2 +- src/{classes => api}/sdo/asset.impl.ts | 0 src/{classes => api}/sdo/campaign.impl.ts | 0 src/{classes => api}/sdo/collection.impl.ts | 0 .../sdo/data-component.impl.ts | 0 src/{classes => api}/sdo/data-source.impl.ts | 0 .../sdo/detection-strategy.impl.ts | 2 +- src/{classes => api}/sdo/group.impl.ts | 0 src/{classes => api}/sdo/identity.impl.ts | 0 src/{classes => api}/sdo/index.ts | 0 src/{classes => api}/sdo/log-source.impl.ts | 0 src/{classes => api}/sdo/malware.impl.ts | 0 src/{classes => api}/sdo/matrix.impl.ts | 0 src/{classes => api}/sdo/mitigation.impl.ts | 0 src/{classes => api}/sdo/tactic.impl.ts | 0 src/{classes => api}/sdo/technique.impl.ts | 0 src/{classes => api}/sdo/tool.impl.ts | 0 src/{classes => api}/smo/index.ts | 0 .../smo/marking-definition.impl.ts | 0 src/{classes => api}/sro/index.ts | 0 src/{classes => api}/sro/relationship.impl.ts | 0 src/{classes => api}/utils.ts | 0 src/index.ts | 2 +- src/main.ts | 34 +++++++++---------- 31 files changed, 22 insertions(+), 22 deletions(-) rename src/{classes => api}/attack-data-model.ts (100%) rename src/{classes => api}/common/attack-object.impl.ts (100%) rename src/{classes => api}/common/index.ts (100%) rename src/{classes => api}/common/validated.ts (100%) rename src/{classes => api}/index.ts (100%) rename src/{classes => api}/sdo/analytic.impl.ts (87%) rename src/{classes => api}/sdo/asset.impl.ts (100%) rename src/{classes => api}/sdo/campaign.impl.ts (100%) rename src/{classes => api}/sdo/collection.impl.ts (100%) rename src/{classes => api}/sdo/data-component.impl.ts (100%) rename src/{classes => api}/sdo/data-source.impl.ts (100%) rename src/{classes => api}/sdo/detection-strategy.impl.ts (93%) rename src/{classes => api}/sdo/group.impl.ts (100%) rename src/{classes => api}/sdo/identity.impl.ts (100%) rename src/{classes => api}/sdo/index.ts (100%) rename src/{classes => api}/sdo/log-source.impl.ts (100%) rename src/{classes => api}/sdo/malware.impl.ts (100%) rename src/{classes => api}/sdo/matrix.impl.ts (100%) rename src/{classes => api}/sdo/mitigation.impl.ts (100%) rename src/{classes => api}/sdo/tactic.impl.ts (100%) rename src/{classes => api}/sdo/technique.impl.ts (100%) rename src/{classes => api}/sdo/tool.impl.ts (100%) rename src/{classes => api}/smo/index.ts (100%) rename src/{classes => api}/smo/marking-definition.impl.ts (100%) rename src/{classes => api}/sro/index.ts (100%) rename src/{classes => api}/sro/relationship.impl.ts (100%) rename src/{classes => api}/utils.ts (100%) diff --git a/docs/USAGE.md b/docs/USAGE.md index ef672177..0c003af8 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -92,7 +92,7 @@ Each sub-package serves a specific purpose: The library is designed with a hierarchical structure. Every directory exports its modules through an `index.ts` file, creating a clear and organized namespace. The top-level `index.ts` file exports all components, allowing for straightforward imports: ```typescript -export * from './classes/index.js'; +export * from './api/index.js'; export * from './data-sources/index.js'; export * from './errors/index.js'; export * from './schemas/index.js'; diff --git a/examples/sdo/technique-impl.example.ts b/examples/sdo/technique-impl.example.ts index 871c582e..e1ce4686 100644 --- a/examples/sdo/technique-impl.example.ts +++ b/examples/sdo/technique-impl.example.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; +import { type TechniqueCls, TechniqueImpl } from "../../src/api/sdo/technique.impl.js"; import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; -import { type TechniqueCls, TechniqueImpl } from "../../src/classes/sdo/technique.impl.js"; /*************************************************************************************************** */ // Example 1: Valid Technique diff --git a/src/classes/attack-data-model.ts b/src/api/attack-data-model.ts similarity index 100% rename from src/classes/attack-data-model.ts rename to src/api/attack-data-model.ts diff --git a/src/classes/common/attack-object.impl.ts b/src/api/common/attack-object.impl.ts similarity index 100% rename from src/classes/common/attack-object.impl.ts rename to src/api/common/attack-object.impl.ts diff --git a/src/classes/common/index.ts b/src/api/common/index.ts similarity index 100% rename from src/classes/common/index.ts rename to src/api/common/index.ts diff --git a/src/classes/common/validated.ts b/src/api/common/validated.ts similarity index 100% rename from src/classes/common/validated.ts rename to src/api/common/validated.ts diff --git a/src/classes/index.ts b/src/api/index.ts similarity index 100% rename from src/classes/index.ts rename to src/api/index.ts diff --git a/src/classes/sdo/analytic.impl.ts b/src/api/sdo/analytic.impl.ts similarity index 87% rename from src/classes/sdo/analytic.impl.ts rename to src/api/sdo/analytic.impl.ts index 343ebbe2..ba435b1e 100644 --- a/src/classes/sdo/analytic.impl.ts +++ b/src/api/sdo/analytic.impl.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import { AttackBaseImpl } from '@/api/common/attack-object.impl.js'; import type { Analytic } from '@/schemas/index.js'; -import { AttackBaseImpl } from '@/classes/common/attack-object.impl.js'; export class AnalyticImpl extends AttackBaseImpl implements Analytic { constructor(readonly analytic: Analytic) { diff --git a/src/classes/sdo/asset.impl.ts b/src/api/sdo/asset.impl.ts similarity index 100% rename from src/classes/sdo/asset.impl.ts rename to src/api/sdo/asset.impl.ts diff --git a/src/classes/sdo/campaign.impl.ts b/src/api/sdo/campaign.impl.ts similarity index 100% rename from src/classes/sdo/campaign.impl.ts rename to src/api/sdo/campaign.impl.ts diff --git a/src/classes/sdo/collection.impl.ts b/src/api/sdo/collection.impl.ts similarity index 100% rename from src/classes/sdo/collection.impl.ts rename to src/api/sdo/collection.impl.ts diff --git a/src/classes/sdo/data-component.impl.ts b/src/api/sdo/data-component.impl.ts similarity index 100% rename from src/classes/sdo/data-component.impl.ts rename to src/api/sdo/data-component.impl.ts diff --git a/src/classes/sdo/data-source.impl.ts b/src/api/sdo/data-source.impl.ts similarity index 100% rename from src/classes/sdo/data-source.impl.ts rename to src/api/sdo/data-source.impl.ts diff --git a/src/classes/sdo/detection-strategy.impl.ts b/src/api/sdo/detection-strategy.impl.ts similarity index 93% rename from src/classes/sdo/detection-strategy.impl.ts rename to src/api/sdo/detection-strategy.impl.ts index 500ff705..c8508ca0 100644 --- a/src/classes/sdo/detection-strategy.impl.ts +++ b/src/api/sdo/detection-strategy.impl.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import { AttackBaseImpl } from '@/api/common/attack-object.impl.js'; import type { DetectionStrategy } from '@/schemas/index.js'; -import { AttackBaseImpl } from '@/classes/common/attack-object.impl.js'; import { TechniqueImpl } from './technique.impl.js'; export class DetectionStrategyImpl extends AttackBaseImpl implements DetectionStrategy { diff --git a/src/classes/sdo/group.impl.ts b/src/api/sdo/group.impl.ts similarity index 100% rename from src/classes/sdo/group.impl.ts rename to src/api/sdo/group.impl.ts diff --git a/src/classes/sdo/identity.impl.ts b/src/api/sdo/identity.impl.ts similarity index 100% rename from src/classes/sdo/identity.impl.ts rename to src/api/sdo/identity.impl.ts diff --git a/src/classes/sdo/index.ts b/src/api/sdo/index.ts similarity index 100% rename from src/classes/sdo/index.ts rename to src/api/sdo/index.ts diff --git a/src/classes/sdo/log-source.impl.ts b/src/api/sdo/log-source.impl.ts similarity index 100% rename from src/classes/sdo/log-source.impl.ts rename to src/api/sdo/log-source.impl.ts diff --git a/src/classes/sdo/malware.impl.ts b/src/api/sdo/malware.impl.ts similarity index 100% rename from src/classes/sdo/malware.impl.ts rename to src/api/sdo/malware.impl.ts diff --git a/src/classes/sdo/matrix.impl.ts b/src/api/sdo/matrix.impl.ts similarity index 100% rename from src/classes/sdo/matrix.impl.ts rename to src/api/sdo/matrix.impl.ts diff --git a/src/classes/sdo/mitigation.impl.ts b/src/api/sdo/mitigation.impl.ts similarity index 100% rename from src/classes/sdo/mitigation.impl.ts rename to src/api/sdo/mitigation.impl.ts diff --git a/src/classes/sdo/tactic.impl.ts b/src/api/sdo/tactic.impl.ts similarity index 100% rename from src/classes/sdo/tactic.impl.ts rename to src/api/sdo/tactic.impl.ts diff --git a/src/classes/sdo/technique.impl.ts b/src/api/sdo/technique.impl.ts similarity index 100% rename from src/classes/sdo/technique.impl.ts rename to src/api/sdo/technique.impl.ts diff --git a/src/classes/sdo/tool.impl.ts b/src/api/sdo/tool.impl.ts similarity index 100% rename from src/classes/sdo/tool.impl.ts rename to src/api/sdo/tool.impl.ts diff --git a/src/classes/smo/index.ts b/src/api/smo/index.ts similarity index 100% rename from src/classes/smo/index.ts rename to src/api/smo/index.ts diff --git a/src/classes/smo/marking-definition.impl.ts b/src/api/smo/marking-definition.impl.ts similarity index 100% rename from src/classes/smo/marking-definition.impl.ts rename to src/api/smo/marking-definition.impl.ts diff --git a/src/classes/sro/index.ts b/src/api/sro/index.ts similarity index 100% rename from src/classes/sro/index.ts rename to src/api/sro/index.ts diff --git a/src/classes/sro/relationship.impl.ts b/src/api/sro/relationship.impl.ts similarity index 100% rename from src/classes/sro/relationship.impl.ts rename to src/api/sro/relationship.impl.ts diff --git a/src/classes/utils.ts b/src/api/utils.ts similarity index 100% rename from src/classes/utils.ts rename to src/api/utils.ts diff --git a/src/index.ts b/src/index.ts index bace3713..28b6252d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export * from '@/classes/index.js'; +export * from '@/api/index.js'; export * from '@/data-sources/index.js'; export * from '@/schemas/index.js'; export * from '@/refinements/index.js'; diff --git a/src/main.ts b/src/main.ts index c8b98305..b54f48a4 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, - type AttackObject, extensibleStixBundleSchema, + type AttackObject, 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 { @@ -34,7 +34,7 @@ import { type ParsingMode, } from './data-sources/data-source-registration.js'; -import { AttackDataModel } from './classes/attack-data-model.js'; +import { AttackDataModel } from './api/attack-data-model.js'; const readFile = async (path: string): Promise => { if (typeof window !== 'undefined') { From ec0f21499c2f8dad27e501cb909b4d6e833ec8a1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:38:48 -0400 Subject: [PATCH 03/13] refactor: move getters and generator to new src/utils package --- src/{generator/index.ts => utils/generator.ts} | 0 src/{api/utils.ts => utils/getters.ts} | 6 +++--- src/utils/index.ts | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) rename src/{generator/index.ts => utils/generator.ts} (100%) rename src/{api/utils.ts => utils/getters.ts} (93%) create mode 100644 src/utils/index.ts diff --git a/src/generator/index.ts b/src/utils/generator.ts similarity index 100% rename from src/generator/index.ts rename to src/utils/generator.ts diff --git a/src/api/utils.ts b/src/utils/getters.ts similarity index 93% rename from src/api/utils.ts rename to src/utils/getters.ts index 730fe0ce..178f62fa 100644 --- a/src/api/utils.ts +++ b/src/utils/getters.ts @@ -1,10 +1,10 @@ +import { DataSourceImpl } from '../api/sdo/data-source.impl.js'; +import { MitigationImpl } from '../api/sdo/mitigation.impl.js'; +import { TacticImpl } from '../api/sdo/tactic.impl.js'; import type { XMitrePlatforms } from '../schemas/common/index.js'; import type { DataSource, Mitigation, Tactic, Technique } from '../schemas/sdo/index.js'; import type { AttackObject } from '../schemas/sdo/stix-bundle.schema.js'; import type { Relationship } from '../schemas/sro/relationship.schema.js'; -import { DataSourceImpl } from './sdo/data-source.impl.js'; -import { MitigationImpl } from './sdo/mitigation.impl.js'; -import { TacticImpl } from './sdo/tactic.impl.js'; export function getSubTechniques( technique: Technique, diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..6c1160a4 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,4 @@ +import { createSyntheticStixObject } from './generator.js'; +import * as getters from './getters.js'; + +export { createSyntheticStixObject, getters }; From 30868f1b5a37dca8e041817e3f2623dae1398654 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:39:29 -0400 Subject: [PATCH 04/13] refactor: consolidate src/data-sources with src/main.ts --- src/data-sources/data-source-registration.ts | 110 ---------------- src/data-sources/fetch-attack-versions.ts | 52 -------- src/data-sources/index.ts | 1 - src/main.ts | 130 ++++++++++++++++++- 4 files changed, 125 insertions(+), 168 deletions(-) delete mode 100644 src/data-sources/data-source-registration.ts delete mode 100644 src/data-sources/fetch-attack-versions.ts delete mode 100644 src/data-sources/index.ts diff --git a/src/data-sources/data-source-registration.ts b/src/data-sources/data-source-registration.ts deleted file mode 100644 index e76a7de3..00000000 --- a/src/data-sources/data-source-registration.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { attackDomainSchema, type AttackDomain } from '../index.js'; -import { fetchAttackVersions } from './fetch-attack-versions.js'; - -export type ParsingMode = 'strict' | 'relaxed'; - -export type DataSourceOptions = - | { - source: 'attack'; - domain: AttackDomain; - version?: string; - parsingMode?: ParsingMode; - } - | { - source: 'file'; - path: string; - parsingMode?: ParsingMode; - } - | { - source: 'url'; - url: string; - parsingMode?: ParsingMode; - } - | { - source: 'taxii'; - url: string; - parsingMode?: ParsingMode; - }; - -/** - * Represents a data source registration with validation logic. - */ -export class DataSourceRegistration { - /** - * Creates a new DataSourceRegistration instance. - * @param options - The data source options to register. - */ - constructor(public readonly options: DataSourceOptions) { - this.validateOptions(); - } - - /** - * Validates the data source options to ensure the correct fields are provided for each source type. - * @throws An error if validation fails. - */ - private async validateOptions(): Promise { - const { source, parsingMode } = this.options; - - // Validate parsing mode - if (parsingMode && !['strict', 'relaxed'].includes(parsingMode)) { - throw new Error(`Invalid parsingMode: ${parsingMode}. Expected 'strict' or 'relaxed'.`); - } - - switch (source) { - case 'attack': { - await this.validateAttackOptions(); - break; - } - case 'file': { - this.validateFileOptions(); - break; - } - case 'url': - case 'taxii': { - throw new Error(`The ${source} source is not implemented yet.`); - } - default: { - throw new Error(`Unsupported data source type: ${source}`); - } - } - } - - /** - * Validates options specific to the 'attack' source type. - * @throws An error if validation fails. - */ - private async validateAttackOptions(): Promise { - const { domain, version } = this.options as { domain: AttackDomain; version?: string }; - - // Validate domain - if (!domain || !Object.values(attackDomainSchema.enum).includes(domain)) { - throw new Error( - `Invalid domain provided for 'attack' source. Expected one of: ${Object.values( - attackDomainSchema.enum, - ).join(', ')}`, - ); - } - - // Validate version if provided - if (version) { - const supportedVersions = await fetchAttackVersions(); - const normalizedVersion = version.replace(/^v/, ''); // Remove leading 'v' if present - if (!supportedVersions.includes(normalizedVersion)) { - throw new Error( - `Invalid version: ${version}. Supported versions are: ${supportedVersions.join(', ')}`, - ); - } - } - } - - /** - * Validates options specific to the 'file' source type. - * @throws An error if validation fails. - */ - private validateFileOptions(): void { - const { path } = this.options as { path: string }; - if (!path) { - throw new Error("The 'file' source requires a 'path' field to specify the file location."); - } - } -} diff --git a/src/data-sources/fetch-attack-versions.ts b/src/data-sources/fetch-attack-versions.ts deleted file mode 100644 index 8e3b7df3..00000000 --- a/src/data-sources/fetch-attack-versions.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Represents a GitHub release object. - */ -interface GitHubRelease { - tag_name: string; - name: string; - published_at: string; -} - -/** - * Normalizes a version string by removing any leading 'v' character. - * @param version - The version string to normalize. - * @returns The normalized version string. - */ -function normalizeVersion(version: string): string { - return version.replace(/^v/, ''); -} - -/** - * Fetches the list of ATT&CK versions from the MITRE ATT&CK STIX data GitHub repository. - * @returns A promise that resolves to an array of version strings. - * @throws An error if the HTTP request fails. - */ -export async function fetchAttackVersions(): Promise { - const url = 'https://api.github.com/repos/mitre-attack/attack-stix-data/releases'; - - // Make a GET request to the GitHub API - const response = await fetch(url, { - headers: { - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const releases: GitHubRelease[] = await response.json(); - - // Extract and normalize version numbers, then sort them in descending order - const versions = releases - .map((release) => normalizeVersion(release.tag_name)) - .sort((a, b) => { - const [aMajor, aMinor] = a.split('.').map(Number); - const [bMajor, bMinor] = b.split('.').map(Number); - if (bMajor !== aMajor) return bMajor - aMajor; - return bMinor - aMinor; - }); - - return versions; -} diff --git a/src/data-sources/index.ts b/src/data-sources/index.ts deleted file mode 100644 index 2b892937..00000000 --- a/src/data-sources/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './data-source-registration.js'; diff --git a/src/main.ts b/src/main.ts index b54f48a4..4f2cb2d5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,12 +29,132 @@ import { toolSchema, } from './schemas/index.js'; -import { - DataSourceRegistration, - type ParsingMode, -} from './data-sources/data-source-registration.js'; - import { AttackDataModel } from './api/attack-data-model.js'; +import { attackDomainSchema, type AttackDomain } from './index.js'; + +export type ParsingMode = 'strict' | 'relaxed'; + +export type DataSourceOptions = + | { + source: 'attack'; + domain: AttackDomain; + version?: string; + parsingMode?: ParsingMode; + } + | { + source: 'file'; + path: string; + parsingMode?: ParsingMode; + } + | { + source: 'url'; + url: string; + parsingMode?: ParsingMode; + } + | { + source: 'taxii'; + url: string; + parsingMode?: ParsingMode; + }; + +interface GitHubRelease { + tag_name: string; + name: string; + published_at: string; +} + +function normalizeVersion(version: string): string { + return version.replace(/^v/, ''); +} + +export async function fetchAttackVersions(): Promise { + const url = 'https://api.github.com/repos/mitre-attack/attack-stix-data/releases'; + + const response = await fetch(url, { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const releases: GitHubRelease[] = await response.json(); + + const versions = releases + .map((release) => normalizeVersion(release.tag_name)) + .sort((a, b) => { + const [aMajor, aMinor] = a.split('.').map(Number); + const [bMajor, bMinor] = b.split('.').map(Number); + if (bMajor !== aMajor) return bMajor - aMajor; + return bMinor - aMinor; + }); + + return versions; +} + +export class DataSourceRegistration { + constructor(public readonly options: DataSourceOptions) { + this.validateOptions(); + } + + private async validateOptions(): Promise { + const { source, parsingMode } = this.options; + + if (parsingMode && !['strict', 'relaxed'].includes(parsingMode)) { + throw new Error(`Invalid parsingMode: ${parsingMode}. Expected 'strict' or 'relaxed'.`); + } + + switch (source) { + case 'attack': { + await this.validateAttackOptions(); + break; + } + case 'file': { + this.validateFileOptions(); + break; + } + case 'url': + case 'taxii': { + throw new Error(`The ${source} source is not implemented yet.`); + } + default: { + throw new Error(`Unsupported data source type: ${source}`); + } + } + } + + private async validateAttackOptions(): Promise { + const { domain, version } = this.options as { domain: AttackDomain; version?: string }; + + if (!domain || !Object.values(attackDomainSchema.enum).includes(domain)) { + throw new Error( + `Invalid domain provided for 'attack' source. Expected one of: ${Object.values( + attackDomainSchema.enum, + ).join(', ')}`, + ); + } + + if (version) { + const supportedVersions = await fetchAttackVersions(); + const normalizedVersion = version.replace(/^v/, ''); + if (!supportedVersions.includes(normalizedVersion)) { + throw new Error( + `Invalid version: ${version}. Supported versions are: ${supportedVersions.join(', ')}`, + ); + } + } + } + + private validateFileOptions(): void { + const { path } = this.options as { path: string }; + if (!path) { + throw new Error("The 'file' source requires a 'path' field to specify the file location."); + } + } +} const readFile = async (path: string): Promise => { if (typeof window !== 'undefined') { From c8ed830721160a1d42f90bbba47998ba88d910d4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:45:49 -0400 Subject: [PATCH 05/13] refactor: move src/refinements to src/schemas/refinements --- src/{ => schemas}/refinements/index.ts | 0 src/schemas/sdo/campaign.schema.ts | 17 +++++++------ src/schemas/sdo/group.schema.ts | 6 ++--- src/schemas/sdo/malware.schema.ts | 16 ++++++------- src/schemas/sdo/stix-bundle.schema.ts | 32 ++++++++++++------------- src/schemas/sdo/technique.schema.ts | 22 ++++++++--------- src/schemas/sdo/tool.schema.ts | 10 ++++---- src/schemas/sro/relationship.schema.ts | 6 ++--- test/objects/analytic.test.ts | 2 +- test/objects/asset.test.ts | 2 +- test/objects/campaign.test.ts | 4 ++-- test/objects/collection.test.ts | 2 +- test/objects/data-component.test.ts | 6 ++--- test/objects/data-source.test.ts | 4 ++-- test/objects/detection-strategy.test.ts | 4 ++-- test/objects/group.test.ts | 4 ++-- test/objects/identity.test.ts | 2 +- test/objects/log-source.test.ts | 4 ++-- test/objects/malware.test.ts | 4 ++-- test/objects/marking-definition.test.ts | 2 +- test/objects/matrix.test.ts | 2 +- test/objects/mitigation.test.ts | 4 ++-- test/objects/relationship.test.ts | 28 +++++++++++----------- test/objects/tactic.test.ts | 2 +- test/objects/technique.test.ts | 2 +- test/objects/tool.test.ts | 2 +- 26 files changed, 96 insertions(+), 93 deletions(-) rename src/{ => schemas}/refinements/index.ts (100%) diff --git a/src/refinements/index.ts b/src/schemas/refinements/index.ts similarity index 100% rename from src/refinements/index.ts rename to src/schemas/refinements/index.ts diff --git a/src/schemas/sdo/campaign.schema.ts b/src/schemas/sdo/campaign.schema.ts index 928710b5..2e22f5e7 100644 --- a/src/schemas/sdo/campaign.schema.ts +++ b/src/schemas/sdo/campaign.schema.ts @@ -1,17 +1,20 @@ +import { + createCitationsRefinement, + createFirstAliasRefinement, +} from '@/schemas/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'; ///////////////////////////////////// // diff --git a/src/schemas/sdo/group.schema.ts b/src/schemas/sdo/group.schema.ts index af6914b9..e6cab173 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 { attackBaseDomainObjectSchema } from '@/schemas/common/attack-base-object.js'; import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; -import { createFirstAliasRefinement } from '@/refinements/index.js'; +import { createFirstAliasRefinement } from '@/schemas/refinements/index.js'; +import { z } from 'zod/v4'; import { aliasesSchema, - createStixIdValidator, createAttackExternalReferencesSchema, + createStixIdValidator, stixTimestampSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, diff --git a/src/schemas/sdo/malware.schema.ts b/src/schemas/sdo/malware.schema.ts index 9111bda0..35061245 100644 --- a/src/schemas/sdo/malware.schema.ts +++ b/src/schemas/sdo/malware.schema.ts @@ -1,6 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/schemas/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'; ///////////////////////////////////// // diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 0fe47694..82efae84 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -1,29 +1,29 @@ +import { createFirstBundleObjectRefinement } from '@/schemas/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 diff --git a/src/schemas/sdo/technique.schema.ts b/src/schemas/sdo/technique.schema.ts index d2c3bffc..2abe0c0d 100644 --- a/src/schemas/sdo/technique.schema.ts +++ b/src/schemas/sdo/technique.schema.ts @@ -1,21 +1,21 @@ +import { + createAttackIdInExternalReferencesRefinement, + createEnterpriseOnlyPropertiesRefinement, + createMobileOnlyPropertiesRefinement, +} from '@/schemas/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'; ///////////////////////////////////// // diff --git a/src/schemas/sdo/tool.schema.ts b/src/schemas/sdo/tool.schema.ts index 05569db8..930ac221 100644 --- a/src/schemas/sdo/tool.schema.ts +++ b/src/schemas/sdo/tool.schema.ts @@ -1,5 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/schemas/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'; ///////////////////////////////////// // diff --git a/src/schemas/sro/relationship.schema.ts b/src/schemas/sro/relationship.schema.ts index 2b4af2de..5d9c278f 100644 --- a/src/schemas/sro/relationship.schema.ts +++ b/src/schemas/sro/relationship.schema.ts @@ -1,3 +1,4 @@ +import { createFoundInRelationshipRefinement } from '@/schemas/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'; ///////////////////////////////////// // diff --git a/test/objects/analytic.test.ts b/test/objects/analytic.test.ts index 2b5c103c..207f6e3d 100644 --- a/test/objects/analytic.test.ts +++ b/test/objects/analytic.test.ts @@ -1,10 +1,10 @@ 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 Analytic, analyticSchema, LogSourceRef } from '../../src/schemas/sdo/analytic.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('analyticSchema', () => { const minimalAnalytic = createSyntheticStixObject('x-mitre-analytic'); diff --git a/test/objects/asset.test.ts b/test/objects/asset.test.ts index 53fbe8af..b74ca895 100644 --- a/test/objects/asset.test.ts +++ b/test/objects/asset.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; +import { createSyntheticStixObject } from '../../src/utils/index'; import { xMitreIdentity } from '../../src/schemas/common/index'; diff --git a/test/objects/campaign.test.ts b/test/objects/campaign.test.ts index 17e1ee96..28b5ee12 100644 --- a/test/objects/campaign.test.ts +++ b/test/objects/campaign.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import type { - StixTimestamp + StixTimestamp } from '../../src/schemas/common/index'; import { type Campaign, campaignSchema } from '../../src/schemas/sdo/campaign.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating the Campaign schema. diff --git a/test/objects/collection.test.ts b/test/objects/collection.test.ts index 632feb17..0eec001a 100644 --- a/test/objects/collection.test.ts +++ b/test/objects/collection.test.ts @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { type Collection, collectionSchema } from '../../src/schemas/sdo/collection.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating the Collection schema. diff --git a/test/objects/data-component.test.ts b/test/objects/data-component.test.ts index 56f7f003..80934888 100644 --- a/test/objects/data-component.test.ts +++ b/test/objects/data-component.test.ts @@ -1,9 +1,9 @@ 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'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('dataComponentSchema', () => { const minimalDataComponent = createSyntheticStixObject('x-mitre-data-component'); diff --git a/test/objects/data-source.test.ts b/test/objects/data-source.test.ts index 3d40cc29..ea7bdc62 100644 --- a/test/objects/data-source.test.ts +++ b/test/objects/data-source.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { - type ExternalReferences + type ExternalReferences } from '../../src/schemas/common/index'; import { type DataSource, dataSourceSchema } from '../../src/schemas/sdo/data-source.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('dataSourceSchema', () => { const minimalDataSource = createSyntheticStixObject('x-mitre-data-source'); diff --git a/test/objects/detection-strategy.test.ts b/test/objects/detection-strategy.test.ts index 9a3031ee..febbbe6c 100644 --- a/test/objects/detection-strategy.test.ts +++ b/test/objects/detection-strategy.test.ts @@ -1,10 +1,10 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { - type ExternalReferences + type ExternalReferences } from '../../src/schemas/common/index'; import { type DetectionStrategy, detectionStrategySchema } from '../../src/schemas/sdo/detection-strategy.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('detectionStrategySchema', () => { const minimalDetectionStrategy = createSyntheticStixObject('x-mitre-detection-strategy'); diff --git a/test/objects/group.test.ts b/test/objects/group.test.ts index 186cd71b..82f90630 100644 --- a/test/objects/group.test.ts +++ b/test/objects/group.test.ts @@ -1,10 +1,10 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import type { - Description + Description } from '../../src/schemas/common/index'; import { type Group, groupSchema } from '../../src/schemas/sdo/group.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating the Group schema. diff --git a/test/objects/identity.test.ts b/test/objects/identity.test.ts index 02893418..fcb37f8d 100644 --- a/test/objects/identity.test.ts +++ b/test/objects/identity.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { type Identity, identitySchema } from '../../src/schemas/sdo/identity.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('IdentitySchema', () => { const minimalIdentity = createSyntheticStixObject('identity'); diff --git a/test/objects/log-source.test.ts b/test/objects/log-source.test.ts index acc819b9..95e54198 100644 --- a/test/objects/log-source.test.ts +++ b/test/objects/log-source.test.ts @@ -1,10 +1,10 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { - type ExternalReferences + type ExternalReferences } from '../../src/schemas/common/index'; import { type LogSource, logSourceSchema } from '../../src/schemas/sdo/log-source.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('logSourceSchema', () => { const minimalLogSource = createSyntheticStixObject('x-mitre-log-source'); diff --git a/test/objects/malware.test.ts b/test/objects/malware.test.ts index be9861a8..5abdd48c 100644 --- a/test/objects/malware.test.ts +++ b/test/objects/malware.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { - type StixTimestamp + type StixTimestamp } from '../../src/schemas/common/index'; import { type Malware, malwareSchema } from '../../src/schemas/sdo/malware.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('MalwareSchema', () => { const minimalMalware = createSyntheticStixObject('malware'); diff --git a/test/objects/marking-definition.test.ts b/test/objects/marking-definition.test.ts index 40002299..eccdf268 100644 --- a/test/objects/marking-definition.test.ts +++ b/test/objects/marking-definition.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { markingDefinitionSchema } from '../../src/schemas/smo/marking-definition.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating MarkingDefinition schema with "statement" type. diff --git a/test/objects/matrix.test.ts b/test/objects/matrix.test.ts index 94709989..42faa50b 100644 --- a/test/objects/matrix.test.ts +++ b/test/objects/matrix.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { type Matrix, matrixSchema } from '../../src/schemas/sdo/matrix.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating the Matrix schema. diff --git a/test/objects/mitigation.test.ts b/test/objects/mitigation.test.ts index 040bb832..b42b9db8 100644 --- a/test/objects/mitigation.test.ts +++ b/test/objects/mitigation.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { - type ExternalReferences + type ExternalReferences } from '../../src/schemas/common/index'; import { type Mitigation, mitigationSchema } from '../../src/schemas/sdo/mitigation.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; describe('MitigationSchema', () => { const minimalMitigation = createSyntheticStixObject('course-of-action'); diff --git a/test/objects/relationship.test.ts b/test/objects/relationship.test.ts index ed895fb3..cfa3916a 100644 --- a/test/objects/relationship.test.ts +++ b/test/objects/relationship.test.ts @@ -1,24 +1,24 @@ import { v4 as uuidv4 } from 'uuid'; 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 { createSyntheticStixObject } from '../../src/utils/index'; import { logger } from '../utils/logger'; describe('RelationshipSchema', () => { diff --git a/test/objects/tactic.test.ts b/test/objects/tactic.test.ts index 24751a12..36a7c00a 100644 --- a/test/objects/tactic.test.ts +++ b/test/objects/tactic.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { type Tactic, tacticSchema } from '../../src/schemas/sdo/tactic.schema'; +import { createSyntheticStixObject } from '../../src/utils/index'; /** * Test suite for validating the Tactic schema. diff --git a/test/objects/technique.test.ts b/test/objects/technique.test.ts index e70c51a1..32e0c993 100644 --- a/test/objects/technique.test.ts +++ b/test/objects/technique.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; +import { createSyntheticStixObject } from '../../src/utils/index'; import { xMitreIdentity } from '../../src/schemas/common/index'; diff --git a/test/objects/tool.test.ts b/test/objects/tool.test.ts index e33c27f4..b336a233 100644 --- a/test/objects/tool.test.ts +++ b/test/objects/tool.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; +import { createSyntheticStixObject } from '../../src/utils/index'; import { type Tool, toolSchema } from '../../src/schemas/sdo/tool.schema'; /** From 58824e82e4047b9eb08fc126296375e5cdb32ea6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:46:31 -0400 Subject: [PATCH 06/13] refactor: update imports in index.ts files --- src/api/index.ts | 4 ++-- src/index.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 73efc168..2c7a93d9 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ +export * from '../utils/getters.js'; +export * from './attack-data-model.js'; export * from './common/index.js'; export * from './sdo/index.js'; export * from './smo/index.js'; -export * from './attack-data-model.js'; -export * from './utils.js'; diff --git a/src/index.ts b/src/index.ts index 28b6252d..c0da346e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from '@/api/index.js'; -export * from '@/data-sources/index.js'; -export * from '@/schemas/index.js'; -export * from '@/refinements/index.js'; export * from '@/main.js'; +export * from '@/schemas/index.js'; +export * from '@/schemas/refinements/index.js'; From ad7c60c27b21f2033c0ca64c2372ecc234b2bf82 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:46:59 -0400 Subject: [PATCH 07/13] docs: update code cell in usage documentation --- docs/USAGE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index 0c003af8..4bebff98 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -93,7 +93,6 @@ The library is designed with a hierarchical structure. Every directory exports i ```typescript export * from './api/index.js'; -export * from './data-sources/index.js'; export * from './errors/index.js'; export * from './schemas/index.js'; export * from './main.js'; From 52f3cf1bf9ab8b399844493f0157cbaa60cd3074 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:24:48 -0400 Subject: [PATCH 08/13] fix: post merge cleanup --- src/api/sdo/technique.impl.ts | 12 ----- src/classes/sdo/technique.impl.ts | 87 ------------------------------- src/data-sources/index.ts | 2 - 3 files changed, 101 deletions(-) delete mode 100644 src/classes/sdo/technique.impl.ts delete mode 100644 src/data-sources/index.ts diff --git a/src/api/sdo/technique.impl.ts b/src/api/sdo/technique.impl.ts index eb1bf645..2c098475 100644 --- a/src/api/sdo/technique.impl.ts +++ b/src/api/sdo/technique.impl.ts @@ -3,7 +3,6 @@ import { techniqueSchema } from '@/schemas/sdo/technique.schema.js'; import type { AttackObject, Technique } from '@/schemas/sdo/index.js'; import type { TacticImpl } from './tactic.impl.js'; import type { MitigationImpl } from './mitigation.impl.js'; -import type { LogSourceImpl } from './log-source.impl.js'; import type { AssetImpl } from './asset.impl.js'; import type { DataComponentImpl } from './data-component.impl.js'; import type { StixModifiedTimestamp, XMitrePlatform } from '@/schemas/common/index.js'; @@ -13,7 +12,6 @@ export class TechniqueImpl extends Validated(techniqueSchema) { #subTechniques: TechniqueImpl[] = []; #tactics: TacticImpl[] = []; #mitigations: MitigationImpl[] = []; - #logSources: LogSourceImpl[] = []; #parentTechnique?: TechniqueImpl; #relatedTechniques: TechniqueImpl[] = []; #targetAssets: AssetImpl[] = []; @@ -43,12 +41,6 @@ export class TechniqueImpl extends Validated(techniqueSchema) { } } - addLogSource(logSource: LogSourceImpl): void { - if (!this.#logSources.includes(logSource)) { - this.#logSources.push(logSource); - } - } - addRelatedTechnique(technique: TechniqueImpl): void { if (!this.#relatedTechniques.includes(technique)) { this.#relatedTechniques.push(technique); @@ -80,10 +72,6 @@ export class TechniqueImpl extends Validated(techniqueSchema) { return [...this.#mitigations]; } - getLogSources(): readonly LogSourceImpl[] { - return [...this.#logSources]; - } - getParentTechnique(): TechniqueImpl | undefined { return this.#parentTechnique; } diff --git a/src/classes/sdo/technique.impl.ts b/src/classes/sdo/technique.impl.ts deleted file mode 100644 index eed0ed98..00000000 --- a/src/classes/sdo/technique.impl.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ - -import type { Technique } from '../../schemas/sdo/technique.schema.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 _parentTechnique?: TechniqueImpl; - private _relatedTechniques: TechniqueImpl[] = []; - private _targetAssets: AssetImpl[] = []; - private _detectingDataComponents: DataComponentImpl[] = []; - - constructor(readonly technique: Technique) { - super(); - // Assign properties from the Technique object to this instance - Object.assign(this, technique); - } - - setParent(parent: TechniqueImpl): void { - this._parentTechnique = parent; - } - - addSubTechnique(subTechnique: TechniqueImpl): void { - this._subTechniques.push(subTechnique); - } - - addTactic(tactic: TacticImpl): void { - this._tactics.push(tactic); - } - - addMitigation(mitigation: MitigationImpl): void { - this._mitigations.push(mitigation); - } - - addRelatedTechnique(technique: TechniqueImpl): void { - this._relatedTechniques.push(technique); - } - - addTargetAsset(asset: AssetImpl): void { - this._targetAssets.push(asset); - } - - addDetectingDataComponent(dataComponent: DataComponentImpl): void { - this._detectingDataComponents.push(dataComponent); - } - - // Getters - public getSubTechniques(): TechniqueImpl[] { - return this._subTechniques; - } - - getTactics(): TacticImpl[] { - return this._tactics; - } - - getMitigations(): MitigationImpl[] { - return this._mitigations; - } - - getParentTechnique(): TechniqueImpl | undefined { - return this._parentTechnique; - } - - getRelatedTechniques(): TechniqueImpl[] { - return this._relatedTechniques; - } - - getTargetAssets(): AssetImpl[] { - return this._targetAssets; - } - - getDetectingDataComponents(): DataComponentImpl[] { - return this._detectingDataComponents; - } -} - -// Suppress the lint error for the empty interface -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface TechniqueImpl extends Technique {} - -/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */ diff --git a/src/data-sources/index.ts b/src/data-sources/index.ts deleted file mode 100644 index a7eea2fb..00000000 --- a/src/data-sources/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './data-source-registration.js'; -export * from './fetch-attack-versions.js'; From 7b10be2dd1496b1234dbb35f3a1b32ed6d9832fa Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:35:54 -0400 Subject: [PATCH 09/13] refactor(api): rename data source concepts to content origin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename DataSourceOptions → ContentOriginOptions - Rename DataSourceRegistration → ContentOriginRegistration - Rename registerDataSource() → registerContentOrigin() - Rename DataSourceMap → ContentOriginMap - Rename dataSources variable → contentOrigins - Change 'attack' source option to 'mitre' for clarity - Update all documentation in README.md, docs/, and docusaurus/docs/ - Update error messages and comments throughout codebase BREAKING CHANGE: API functions and types have been renamed. Update imports to use ContentOriginRegistration and registerContentOrigin instead of DataSourceRegistration and registerDataSource. Change source: 'attack' to source: 'mitre' in configurations. --- README.md | 30 +++++----- docs/USAGE.md | 40 ++++++------- .../how-to-guides/manage-data-sources.mdx | 46 +++++++-------- .../docs/reference/api/data-sources.mdx | 48 +++++++-------- src/main.ts | 58 ++++++++++--------- 5 files changed, 112 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 59052047..020fd25a 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ Built on STIX 2.1 compliance, it uses Zod schemas and TypeScript types to ensure - **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). +- **Supports Multiple Content Origins**: Load ATT&CK datasets from different content origins, including GitHub, local files, URLs, and TAXII 2.1 servers (more content origins in development). - Parsing, validation, and serialization of ATT&CK data - ES6 classes for object-oriented data manipulation -## Supported Data Sources +## Supported Content Origins -- **`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. +- **`mitre`**: 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. @@ -90,17 +90,17 @@ For most users, we recommend: Example of loading the latest ATT&CK data: ```javascript -import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; -const dataSource = new DataSourceRegistration({ - source: 'attack', +const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '17.1', parsingMode: 'strict' }); -const dataSource = await registerDataSource(dataSource); -const attackEnterpriseLatest = loadDataModel(dataSource); +const dataSourceId = await registerContentOrigin(contentOrigin); +const attackEnterpriseLatest = loadDataModel(dataSourceId); ``` For more details on version compatibility, see the [Compatibility Guide](./COMPATIBILITY.md). @@ -132,21 +132,21 @@ 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, DataSourceRegistration } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; (async () => { - // 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 + // Instantiating a ContentOriginRegistration object will validate that the content origin is accessible and readable + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', // 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 parsingMode: 'relaxed' // 'strict' or 'relaxed' - 'relaxed' mode will attempt to parse and serialize data even if it contains errors or warnings }); try { - // Register the data source and retrieve the unique ID - const uuid = await registerDataSource(dataSource); + // Register the content origin and retrieve the unique ID + const uuid = await registerContentOrigin(contentOrigin); if (uuid) { // Load the dataset using the unique ID const attackEnterpriseLatest = loadDataModel(uuid); @@ -217,7 +217,7 @@ For more detailed examples, please refer to the [examples](./examples/README.md) ## How It Works -1. **Data Registration**: Datasets are registered via `registerDataSource`. You specify the source of the data (e.g., `attack`, `file`, `url`, `taxii`) and provide any necessary options (such as `domain` and `version` for ATT&CK datasets). This function returns a unique identifier for the registered data source. +1. **Content Origin Registration**: Datasets are registered via `registerContentOrigin`. You specify the content origin (e.g., `mitre`, `file`, `url`, `taxii`) and provide any necessary options (such as `domain` and `version` for ATT&CK datasets). This function returns a unique identifier for the registered content origin. 2. **Data Loading**: The `loadDataModel` function is used to load registered data models by their unique identifier. 3. **Parsing and Validation**: Once the data is loaded, it is parsed by Zod schemas, ensuring that the data conforms to the expected STIX 2.1 specification. 4. **Serialization**: Valid objects are converted into TypeScript class instances, allowing for type-safe interaction and relationship navigation. diff --git a/docs/USAGE.md b/docs/USAGE.md index e567b54c..a30f07d0 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -9,7 +9,7 @@ The ATT&CK Data Model (ADM) TypeScript API provides a structured and type-safe w - **Type-Safe Data Parsing**: Validates STIX 2.1 data using Zod schemas, ensuring compliance with the ATT&CK Data Model. - **Object-Oriented Interface**: Provides ES6 class wrappers for ATT&CK objects, enabling intuitive interaction and relationship navigation. - **Relationship Mapping**: Automatically processes relationships between objects, allowing easy traversal of the ATT&CK data model. -- **Flexible Data Sources**: Supports loading data from various sources, including the official MITRE ATT&CK GitHub repository, local files, URLs, and TAXII 2.1 servers (some data sources are under development). +- **Flexible Content Origins**: Supports loading data from various content origins, including the official MITRE ATT&CK GitHub repository, local files, URLs, and TAXII 2.1 servers (some content origins are under development). ## Installation @@ -65,7 +65,7 @@ When installed, the library has the following directory structure: │ ├── sdo │ ├── smo │ └── sro -├── data-sources +├── content-origins ├── errors └── schemas ├── common @@ -84,7 +84,7 @@ Each sub-package serves a specific purpose: - **`classes`**: Contains ES6 class wrappers for ATT&CK objects, providing methods for relationship navigation and data manipulation. - **`common`**: Base classes and shared components. - **`sdo`**, **`smo`**, **`sro`**: Class implementations corresponding to the schemas. -- **`data-sources`**: Modules for loading ATT&CK data from various sources. +- **`content-origins`**: Modules for loading ATT&CK data from various content origins. - **`errors`**: Custom error classes used throughout the library. ### Hierarchical Structure @@ -209,20 +209,20 @@ console.log(attackDataModel.campaigns); // Access campaigns ### Initializing with Data -To use the `AttackDataModel`, you need to load it with data from a data source: +To use the `AttackDataModel`, you need to load it with data from a content origin: ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; (async () => { - const dataSource = new DataSource({ - source: 'attack', + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '15.1', parsingMode: 'relaxed', }); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const attackDataModel = loadDataModel(uuid); // Now you can interact with the data model @@ -288,13 +288,13 @@ console.log(campaign.name); const techniques = campaign.getTechniques(); ``` -## Data Sources +## Content Origins -The library supports loading data from various sources through the `DataSource` class. +The library supports loading data from various content origins through the `ContentOriginRegistration` class. -### Supported Data Sources +### Supported Content Origins -- **`attack`**: Official MITRE ATT&CK STIX 2.1 GitHub repository. +- **`mitre`**: Official MITRE ATT&CK STIX 2.1 GitHub repository. - **`file`**: (Coming soon) Local JSON files containing STIX 2.1 bundles. - **`url`**: (Coming soon) URLs serving STIX 2.1 content. - **`taxii`**: (Coming soon) TAXII 2.1 servers. @@ -302,17 +302,17 @@ The library supports loading data from various sources through the `DataSource` ### Loading Data from the ATT&CK GitHub Repository ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; (async () => { - const dataSource = new DataSource({ - source: 'attack', + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '15.1', parsingMode: 'relaxed', }); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const attackDataModel = loadDataModel(uuid); // Access ATT&CK objects @@ -399,18 +399,18 @@ try { ## Advanced Usage -### Custom Data Sources +### Custom Content Origins -You can create custom data sources by extending the `DataSource` class or by providing your own data loading logic. +You can create custom content origins by extending the `ContentOriginRegistration` class or by providing your own data loading logic. ```typescript import { DataSource } from '@mitre-attack/attack-data-model'; -class CustomDataSource extends DataSource { +class CustomContentOrigin extends ContentOriginRegistration { // Implement custom data loading logic } -const customDataSource = new CustomDataSource({ +const customContentOrigin = new CustomContentOrigin({ source: 'custom', // ... other options }); diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.mdx b/docusaurus/docs/how-to-guides/manage-data-sources.mdx index df79d617..79ff8940 100644 --- a/docusaurus/docs/how-to-guides/manage-data-sources.mdx +++ b/docusaurus/docs/how-to-guides/manage-data-sources.mdx @@ -1,12 +1,12 @@ import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; -# How to Manage Data Sources +# How to Manage Content Origins -**Switch between different ATT&CK data sources efficiently** +**Switch between different ATT&CK content origins 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. +This guide shows you how to manage multiple ATT&CK content origins, switch between different versions, and work with local files, URLs, and the official repository. ## Problem Scenarios @@ -15,15 +15,15 @@ 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 +- Manage multiple content origins in a production application +- Cache and reuse content origins efficiently ## Switch Between ATT&CK Versions ### Compare Multiple Versions ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; async function compareVersions() { const versions = ['15.0', '15.1']; @@ -31,14 +31,14 @@ async function compareVersions() { // Load multiple versions for (const version of versions) { - const dataSource = new DataSource({ - source: 'attack', + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: version, parsingMode: 'relaxed' }); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); models[version] = loadDataModel(uuid); } @@ -55,7 +55,7 @@ async function compareVersions() { ```typescript // Omit version to get the latest available const latestDataSource = new DataSource({ - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', // No version specified = latest parsingMode: 'relaxed' @@ -130,7 +130,7 @@ async function loadFromUrl() { ```typescript async function loadWithAuth() { - const dataSource = new DataSource({ + const contentOrigin = new ContentOriginRegistration({ source: 'url', url: 'https://private-server.com/attack-data.json', requestOptions: { @@ -142,7 +142,7 @@ async function loadWithAuth() { parsingMode: 'strict' }); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); return loadDataModel(uuid); } ``` @@ -157,7 +157,7 @@ class AttackDataManager { async registerSource(name: string, config: any): Promise { const dataSource = new DataSource(config); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); this.dataSources.set(name, uuid); return uuid; } @@ -173,14 +173,14 @@ class AttackDataManager { async setupCommonSources() { // Enterprise latest await this.registerSource('enterprise-latest', { - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', parsingMode: 'relaxed' }); // Enterprise v15.0 await this.registerSource('enterprise-v15', { - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', version: '15.0', parsingMode: 'relaxed' @@ -188,7 +188,7 @@ class AttackDataManager { // Mobile latest await this.registerSource('mobile-latest', { - source: 'attack', + source: 'mitre', domain: 'mobile-attack', parsingMode: 'relaxed' }); @@ -212,13 +212,13 @@ async function loadWithFallback() { const fallbackSources = [ // Try latest first { - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', parsingMode: 'relaxed' }, // Fallback to specific version { - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', version: '15.1', parsingMode: 'relaxed' @@ -234,7 +234,7 @@ async function loadWithFallback() { for (const config of fallbackSources) { try { const dataSource = new DataSource(config); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const model = loadDataModel(uuid); console.log(`Successfully loaded from source: ${config.source}`); @@ -256,7 +256,7 @@ async function loadWithFallback() { async function validateDataSource(config: any): Promise { try { const dataSource = new DataSource(config); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const model = loadDataModel(uuid); // Basic validation checks @@ -303,7 +303,7 @@ class CachedDataManager { // Load fresh data console.log('🌐 Loading fresh data'); const dataSource = new DataSource(config); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const model = loadDataModel(uuid); // Cache the result @@ -330,7 +330,7 @@ function getDataSourceConfig(): any { switch (environment) { case 'production': return { - source: 'attack', + source: 'mitre', domain: 'enterprise-attack', version: '15.1', // Pin version in production parsingMode: 'strict' // Strict validation in production @@ -370,7 +370,7 @@ async function loadWithMonitoring(config: any) { console.log('📡 Starting data source load:', config.source); const dataSource = new DataSource(config); - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); const model = loadDataModel(uuid); const loadTime = Date.now() - startTime; diff --git a/docusaurus/docs/reference/api/data-sources.mdx b/docusaurus/docs/reference/api/data-sources.mdx index 7a81b017..3ff5b2e1 100644 --- a/docusaurus/docs/reference/api/data-sources.mdx +++ b/docusaurus/docs/reference/api/data-sources.mdx @@ -1,24 +1,24 @@ import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; -# DataSource +# ContentOriginRegistration -**Data source configuration and registration for loading ATT&CK datasets** +**Content origin 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. +The `ContentOriginRegistration` class defines where and how to load ATT&CK data. It supports multiple content origin types including the official ATT&CK repository, local files, remote URLs, and TAXII servers. ## Constructor ```typescript -new DataSource(options: DataSourceOptions) +new ContentOriginRegistration(options: ContentOriginOptions) ``` -### DataSourceOptions Interface +### ContentOriginOptions Interface ```typescript -interface DataSourceOptions { - source: 'attack' | 'file' | 'url' | 'taxii'; +interface ContentOriginOptions { + source: 'mitre' | 'file' | 'url' | 'taxii'; parsingMode?: 'strict' | 'relaxed'; // Attack source options @@ -45,13 +45,13 @@ interface DataSourceOptions { ## Source Types -### Attack Repository Source +### MITRE Repository Source Load data from the official MITRE ATT&CK STIX 2.1 repository. ```typescript -const dataSource = new DataSource({ - source: 'attack', +const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '15.1', parsingMode: 'strict' @@ -62,7 +62,7 @@ const dataSource = new DataSource({ | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| -| `source` | `'attack'` | ✅ | - | Specifies ATT&CK repository source | +| `source` | `'mitre'` | ✅ | - | Specifies MITRE 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 | @@ -85,7 +85,7 @@ const dataSource = new DataSource({ Load data from local STIX 2.1 bundle files. ```typescript -const dataSource = new DataSource({ +const contentOrigin = new ContentOriginRegistration({ source: 'file', file: '/path/to/enterprise-attack.json', parsingMode: 'relaxed' @@ -129,7 +129,7 @@ const dataSource = new DataSource({ Load data from remote URLs serving STIX 2.1 content. ```typescript -const dataSource = new DataSource({ +const contentOrigin = new ContentOriginRegistration({ source: 'url', url: 'https://example.com/attack-data.json', timeout: 30000, @@ -162,7 +162,7 @@ const dataSource = new DataSource({ Load data from TAXII 2.1 servers. ```typescript -const dataSource = new DataSource({ +const contentOrigin = new ContentOriginRegistration({ source: 'taxii', server: 'https://cti-taxii.mitre.org', collection: 'attack-patterns', @@ -217,38 +217,38 @@ parsingMode: 'relaxed' ## Registration and Loading -### registerDataSource() +### registerContentOrigin() -Validates and registers a data source for use. +Validates and registers a content origin for use. ```typescript -async function registerDataSource(dataSource: DataSource): Promise +async function registerContentOrigin(contentOrigin: ContentOriginRegistration): Promise ``` **Parameters**: -- `dataSource` - Configured DataSource instance +- `contentOrigin` - Configured ContentOriginRegistration instance **Returns**: -- `string` - UUID for the registered data source on success +- `string` - UUID for the registered content origin on success - `null` - Registration failed **Example**: ```typescript -import { registerDataSource } from '@mitre-attack/attack-data-model'; +import { registerContentOrigin } from '@mitre-attack/attack-data-model'; -const dataSource = new DataSource({ - source: 'attack', +const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '15.1' }); try { - const uuid = await registerDataSource(dataSource); + const uuid = await registerContentOrigin(contentOrigin); if (uuid) { - console.log(`Data source registered: ${uuid}`); + console.log(`Content origin registered: ${uuid}`); } else { console.error('Registration failed'); } diff --git a/src/main.ts b/src/main.ts index 50394297..036226c0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,9 +32,9 @@ import { attackDomainSchema, type AttackDomain } from './index.js'; export type ParsingMode = 'strict' | 'relaxed'; -export type DataSourceOptions = +export type ContentOriginOptions = | { - source: 'attack'; + source: 'mitre'; domain: AttackDomain; version?: string; parsingMode?: ParsingMode; @@ -93,8 +93,8 @@ export async function fetchAttackVersions(): Promise { return versions; } -export class DataSourceRegistration { - constructor(public readonly options: DataSourceOptions) { +export class ContentOriginRegistration { + constructor(public readonly options: ContentOriginOptions) { this.validateOptions(); } @@ -106,8 +106,8 @@ export class DataSourceRegistration { } switch (source) { - case 'attack': { - await this.validateAttackOptions(); + case 'mitre': { + await this.validateMitreOptions(); break; } case 'file': { @@ -119,17 +119,17 @@ export class DataSourceRegistration { throw new Error(`The ${source} source is not implemented yet.`); } default: { - throw new Error(`Unsupported data source type: ${source}`); + throw new Error(`Unsupported content origin type: ${source}`); } } } - private async validateAttackOptions(): Promise { + private async validateMitreOptions(): Promise { const { domain, version } = this.options as { domain: AttackDomain; version?: string }; if (!domain || !Object.values(attackDomainSchema.enum).includes(domain)) { throw new Error( - `Invalid domain provided for 'attack' source. Expected one of: ${Object.values( + `Invalid domain provided for 'mitre' source. Expected one of: ${Object.values( attackDomainSchema.enum, ).join(', ')}`, ); @@ -179,31 +179,33 @@ if (typeof window == 'undefined') { 'https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master'; } -interface DataSourceMap { +interface ContentOriginMap { [key: string]: { id: string; model: AttackDataModel; }; } -// Data structure to track registered data sources -const dataSources: DataSourceMap = {}; +// Data structure to track registered content origins +const contentOrigins: ContentOriginMap = {}; /** - * Registers a new data source by fetching and caching ATT&CK data based on the provided options. - * Generates a unique ID for each registered data source. + * Registers a new content origin by fetching and caching ATT&CK data based on the provided options. + * Generates a unique ID for each registered content origin. * - * @param registration - A DataSourceRegistration object containing the source, domain, version, etc. - * @returns The unique ID of the registered data source. + * @param registration - A ContentOriginRegistration object containing the source, domain, version, etc. + * @returns The unique ID of the registered content origin. */ -export async function registerDataSource(registration: DataSourceRegistration): Promise { +export async function registerContentOrigin( + registration: ContentOriginRegistration, +): Promise { const { source, parsingMode = 'strict' } = registration.options; let rawData: StixBundle; - const uniqueId = uuidv4(); // Generate a unique ID for the data source + const uniqueId = uuidv4(); // Generate a unique ID for the content origin switch (source) { - case 'attack': { + case 'mitre': { const { domain, version } = registration.options; rawData = await fetchAttackDataFromGitHub(domain, version); break; @@ -220,7 +222,7 @@ export async function registerDataSource(registration: DataSourceRegistration): break; } default: - throw new Error(`Unsupported source type: ${source}`); + throw new Error(`Unsupported content origin type: ${source}`); } console.log('Retrieved data'); @@ -232,10 +234,10 @@ export async function registerDataSource(registration: DataSourceRegistration): const model = new AttackDataModel(uniqueId, parsedAttackObjects); console.log('Initialized data model.'); - // Store the model and its unique ID in the dataSources map - dataSources[uniqueId] = { id: uniqueId, model }; + // Store the model and its unique ID in the contentOrigins map + contentOrigins[uniqueId] = { id: uniqueId, model }; - return uniqueId; // Return the unique identifier of the data source + return uniqueId; // Return the unique identifier of the content origin } /** @@ -438,15 +440,15 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO } /** - * Returns the data model of the registered data source, given the data source's unique ID. + * Returns the data model of the registered content origin, given the content origin's unique ID. * * @param id - The unique ID of the data model to retrieve. * @returns The corresponding AttackDataModel instance. */ export function loadDataModel(id: string): AttackDataModel { - const dataSource = dataSources[id]; - if (!dataSource) { - throw new Error(`Data source with ID ${id} not found.`); + const contentOrigin = contentOrigins[id]; + if (!contentOrigin) { + throw new Error(`Content origin with ID ${id} not found.`); } - return dataSource.model; + return contentOrigin.model; } From 76111f442ca6c3723914c1e8cf5def02623b27cd Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:40:53 -0400 Subject: [PATCH 10/13] fix: update import paths for refinements - Refinements have been moved from @/refinements to @/schemas/refinements - Update downstream references accordingly --- src/schemas/sdo/campaign.schema.ts | 5 ++++- src/schemas/sdo/group.schema.ts | 2 +- src/schemas/sdo/malware.schema.ts | 2 +- src/schemas/sdo/stix-bundle.schema.ts | 6 +++--- src/schemas/sdo/technique.schema.ts | 2 +- src/schemas/sdo/tool.schema.ts | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/schemas/sdo/campaign.schema.ts b/src/schemas/sdo/campaign.schema.ts index 1a0051b2..fd931ef0 100644 --- a/src/schemas/sdo/campaign.schema.ts +++ b/src/schemas/sdo/campaign.schema.ts @@ -1,4 +1,7 @@ -import { createCitationsRefinement, createFirstAliasRefinement } from '@/refinements/index.js'; +import { + createCitationsRefinement, + createFirstAliasRefinement, +} from '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; import { diff --git a/src/schemas/sdo/group.schema.ts b/src/schemas/sdo/group.schema.ts index 4d6d04fb..47d3b829 100644 --- a/src/schemas/sdo/group.schema.ts +++ b/src/schemas/sdo/group.schema.ts @@ -1,6 +1,6 @@ -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 '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { aliasesSchema, diff --git a/src/schemas/sdo/malware.schema.ts b/src/schemas/sdo/malware.schema.ts index 3fe7d662..6d05a011 100644 --- a/src/schemas/sdo/malware.schema.ts +++ b/src/schemas/sdo/malware.schema.ts @@ -1,7 +1,7 @@ import { createFirstAliasRefinement, createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +} from '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { createAttackExternalReferencesSchema, diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 567db6c3..91b92b3f 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -1,12 +1,12 @@ -import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; +import { createFirstBundleObjectRefinement } from '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { createStixIdValidator } from '../common/stix-identifier.js'; import { createStixTypeValidator } from '../common/stix-type.js'; import { - markingDefinitionSchema, type MarkingDefinition, + markingDefinitionSchema, } from '../smo/marking-definition.schema.js'; -import { relationshipSchema, type Relationship } from '../sro/relationship.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'; diff --git a/src/schemas/sdo/technique.schema.ts b/src/schemas/sdo/technique.schema.ts index 4d00e635..2dfbd21b 100644 --- a/src/schemas/sdo/technique.schema.ts +++ b/src/schemas/sdo/technique.schema.ts @@ -2,7 +2,7 @@ import { createAttackIdInExternalReferencesRefinement, createEnterpriseOnlyPropertiesRefinement, createMobileOnlyPropertiesRefinement, -} from '@/refinements/index.js'; +} from '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, diff --git a/src/schemas/sdo/tool.schema.ts b/src/schemas/sdo/tool.schema.ts index c0e5c143..59bc266a 100644 --- a/src/schemas/sdo/tool.schema.ts +++ b/src/schemas/sdo/tool.schema.ts @@ -1,7 +1,7 @@ import { createFirstAliasRefinement, createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +} from '@/schemas/refinements/index.js'; import { z } from 'zod/v4'; import { createAttackExternalReferencesSchema, From 0dd94d19caa954fc9123bfff57e40caf5e3b89bb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:43:37 -0400 Subject: [PATCH 11/13] fix: update import paths for generator - Generator has been moved to the utils sub-package - Update downstream references accordingly --- test/objects/analytic.test.ts | 2 +- test/objects/asset.test.ts | 2 +- test/objects/campaign.test.ts | 3 +-- test/objects/detection-strategy.test.ts | 6 +++--- test/objects/technique.test.ts | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/objects/analytic.test.ts b/test/objects/analytic.test.ts index ac81620a..8ed8f770 100644 --- a/test/objects/analytic.test.ts +++ b/test/objects/analytic.test.ts @@ -1,12 +1,12 @@ 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 Analytic, analyticSchema, LogSourceReference, } from '../../src/schemas/sdo/analytic.schema'; +import { createSyntheticStixObject } from '../../src/utils/generator'; describe('analyticSchema', () => { const minimalAnalytic = createSyntheticStixObject('x-mitre-analytic'); diff --git a/test/objects/asset.test.ts b/test/objects/asset.test.ts index 738254e1..b40a3ad3 100644 --- a/test/objects/asset.test.ts +++ b/test/objects/asset.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Asset, assetSchema } from '../../src/schemas/sdo/asset.schema'; +import { createSyntheticStixObject } from '../../src/utils/generator'; /** * Test suite for validating the Asset schema. diff --git a/test/objects/campaign.test.ts b/test/objects/campaign.test.ts index d5dd882e..cb483539 100644 --- a/test/objects/campaign.test.ts +++ b/test/objects/campaign.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import type { StixTimestamp } from '../../src/schemas/common/index'; import { type Campaign, campaignSchema } from '../../src/schemas/sdo/campaign.schema'; -import { createSyntheticStixObject } from '../../src/utils/index'; +import { createSyntheticStixObject } from '../../src/utils/generator'; /** * Test suite for validating the Campaign schema. diff --git a/test/objects/detection-strategy.test.ts b/test/objects/detection-strategy.test.ts index 17b60e19..084b81d6 100644 --- a/test/objects/detection-strategy.test.ts +++ b/test/objects/detection-strategy.test.ts @@ -1,11 +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 DetectionStrategy, - detectionStrategySchema, + type DetectionStrategy, + detectionStrategySchema, } from '../../src/schemas/sdo/detection-strategy.schema'; +import { createSyntheticStixObject } from '../../src/utils/generator'; describe('detectionStrategySchema', () => { const minimalDetectionStrategy = createSyntheticStixObject('x-mitre-detection-strategy'); diff --git a/test/objects/technique.test.ts b/test/objects/technique.test.ts index fe5a0610..3785416f 100644 --- a/test/objects/technique.test.ts +++ b/test/objects/technique.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { createSyntheticStixObject } from '../../src/generator'; import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Technique, techniqueSchema } from '../../src/schemas/sdo/technique.schema'; +import { createSyntheticStixObject } from '../../src/utils/generator'; /** * Test suite for validating the Technique schema. From 1f7c3b2577aaef9aff4f1a8d0167afb3bd2f2c8e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:53:58 -0400 Subject: [PATCH 12/13] fix: update documentation unit tests - Update test modules in test/documentation - Update references to content origin (from data source) - Update import paths - Fix some small type hinting issues --- test/documentation/README.test.ts | 34 +++++++++--------- test/documentation/USAGE.test.ts | 39 ++++++++++---------- test/documentation/first-query.test.ts | 50 +++++++++++++------------- 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/test/documentation/README.test.ts b/test/documentation/README.test.ts index c991056e..04f3c608 100644 --- a/test/documentation/README.test.ts +++ b/test/documentation/README.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; import { z } from 'zod'; -import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { createSyntheticStixObject } from '../../src/utils/generator.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'; +import { ContentOriginRegistration } from '../../src/main.js'; +import { AttackDataModel } from '../../src/api/attack-data-model.js'; describe('README.md Code Examples', () => { describe('Installation Examples', () => { @@ -39,36 +39,36 @@ 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 - // Code block: ```javascript const dataSource = new DataSourceRegistration({ source: 'attack', ... }); - const dataSource = new DataSourceRegistration({ - source: 'attack', + // Code block: ```javascript const contentOrigin = new ContentOriginRegistration({ source: 'mitre', ... }); + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', 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'); + expect(contentOrigin.options.source).toBe('mitre'); + expect((contentOrigin.options as any).domain).toBe('enterprise-attack'); + expect((contentOrigin.options as any).version).toBe('17.1'); + expect(contentOrigin.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', + // Code block: ```typescript const contentOrigin = new ContentOriginRegistration({ ..., parsingMode: 'relaxed' }); + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', 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'); + expect(contentOrigin.options.source).toBe('mitre'); + expect((contentOrigin.options as any).domain).toBe('enterprise-attack'); + expect((contentOrigin.options as any).version).toBe('15.1'); + expect(contentOrigin.options.parsingMode).toBe('relaxed'); }); it('should validate that real ATT&CK objects have documented structure', () => { diff --git a/test/documentation/USAGE.test.ts b/test/documentation/USAGE.test.ts index e6acdfe0..580d9c8e 100644 --- a/test/documentation/USAGE.test.ts +++ b/test/documentation/USAGE.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; import { z } from 'zod'; -import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { createSyntheticStixObject } from '../../src/utils/generator.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'; +import { ContentOriginRegistration } from '../../src/main.js'; +import { AttackDataModel } from '../../src/api/attack-data-model.js'; describe('docs/USAGE.md Code Examples', () => { describe('Module Format Support Examples', () => { @@ -36,7 +36,7 @@ describe('docs/USAGE.md Code Examples', () => { expect(campaignSchema).toBeDefined(); expect(techniqueSchema).toBeDefined(); expect(AttackDataModel).toBeDefined(); - expect(DataSourceRegistration).toBeDefined(); + expect(ContentOriginRegistration).toBeDefined(); }); }); @@ -140,21 +140,20 @@ describe('docs/USAGE.md Code Examples', () => { }); describe('Initializing with Data Examples', () => { - it('should support DataSource configuration patterns', () => { + it('should support ContentOriginRegistration 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', + // Code block: ```typescript const contentOrigin = new ContentOriginRegistration({ source: 'mitre', ... }); + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', 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'); + expect(contentOrigin.options.source).toBe('mitre'); + expect((contentOrigin.options as any).domain).toBe('enterprise-attack'); + expect((contentOrigin.options as any).version).toBe('15.1'); + expect(contentOrigin.options.parsingMode).toBe('relaxed'); }); }); @@ -227,19 +226,19 @@ describe('docs/USAGE.md Code Examples', () => { }); }); - describe('Data Sources Examples', () => { - it('should support data source patterns mentioned in USAGE', () => { - // Maps to: docs/USAGE.md - "Data Sources" section + describe('Content Origins Examples', () => { + it('should support content origin patterns mentioned in USAGE', () => { + // Maps to: docs/USAGE.md - "Content Origins" section // Text: "Loading Data from the ATT&CK GitHub Repository" - const dataSource = new DataSourceRegistration({ - source: 'attack', + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '15.1', parsingMode: 'relaxed', }); - expect(dataSource.options.source).toBe('attack'); - expect(dataSource.options.parsingMode).toBe('relaxed'); + expect(contentOrigin.options.source).toBe('mitre'); + expect(contentOrigin.options.parsingMode).toBe('relaxed'); }); }); }); diff --git a/test/documentation/first-query.test.ts b/test/documentation/first-query.test.ts index 178b410f..9607f00e 100644 --- a/test/documentation/first-query.test.ts +++ b/test/documentation/first-query.test.ts @@ -1,23 +1,23 @@ 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'; +import { ContentOriginRegistration } from '../../src/main.js'; +import { AttackDataModel } from '../../src/api/attack-data-model.js'; describe('examples/first-query.ts Code Example', () => { - describe('DataSourceRegistration Configuration', () => { + describe('ContentOriginRegistration 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', + // Code: const contentOrigin = new ContentOriginRegistration({ source: 'mitre', domain: 'enterprise-attack', version: '17.1', parsingMode: 'relaxed' }); + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', 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'); + expect(contentOrigin.options.source).toBe('mitre'); + expect((contentOrigin.options as any).domain).toBe('enterprise-attack'); + expect((contentOrigin.options as any).version).toBe('17.1'); + expect(contentOrigin.options.parsingMode).toBe('relaxed'); }); }); @@ -92,24 +92,24 @@ describe('examples/first-query.ts Code Example', () => { 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) { ... } } + // Code: async function exploreAttackData() { try { const uuid = await registerContentOrigin(contentOrigin); } catch (error) { ... } } // Test that the async pattern components work const testAsyncPattern = async () => { - const dataSource = new DataSourceRegistration({ - source: 'attack', + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', 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(); + expect(contentOrigin.options.source).toBeDefined(); + expect((contentOrigin.options as any).domain).toBeDefined(); + expect((contentOrigin.options as any).version).toBeDefined(); + expect(contentOrigin.options.parsingMode).toBeDefined(); - return dataSource; + return contentOrigin; }; expect(testAsyncPattern).not.toThrow(); @@ -165,21 +165,21 @@ describe('examples/first-query.ts Code Example', () => { 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'; + // Code: import { registerContentOrigin, loadDataModel, ContentOriginRegistration } from '@mitre-attack/attack-data-model'; // Test that the imported classes/functions exist and are usable - expect(DataSourceRegistration).toBeDefined(); - expect(typeof DataSourceRegistration).toBe('function'); + expect(ContentOriginRegistration).toBeDefined(); + expect(typeof ContentOriginRegistration).toBe('function'); - // Test that DataSourceRegistration can be instantiated as shown in the example - const dataSource = new DataSourceRegistration({ - source: 'attack', + // Test that ContentOriginRegistration can be instantiated as shown in the example + const contentOrigin = new ContentOriginRegistration({ + source: 'mitre', domain: 'enterprise-attack', version: '17.1', parsingMode: 'relaxed', }); - expect(dataSource).toBeInstanceOf(DataSourceRegistration); + expect(contentOrigin).toBeInstanceOf(ContentOriginRegistration); }); }); }); From f8e246d46b4aa005b2c342ffd79d4e054253cce1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:20:24 -0400 Subject: [PATCH 13/13] refactor(api): optimize setter method logic in technique.impl class --- src/api/common/attack-object.impl.ts | 2 +- src/api/sdo/technique.impl.ts | 33 ++++++++++------------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/api/common/attack-object.impl.ts b/src/api/common/attack-object.impl.ts index fd300d85..24d6d2b7 100644 --- a/src/api/common/attack-object.impl.ts +++ b/src/api/common/attack-object.impl.ts @@ -14,7 +14,7 @@ export class AttackBaseImpl { /** * Returns the object that revoked this object. */ - getRevokedBy(): AnyAttackObject | undefined { + getRevokedBy() { return this.revokedBy; } } diff --git a/src/api/sdo/technique.impl.ts b/src/api/sdo/technique.impl.ts index 2c098475..1de81707 100644 --- a/src/api/sdo/technique.impl.ts +++ b/src/api/sdo/technique.impl.ts @@ -1,11 +1,11 @@ -import { Validated } from '../common/index.js'; +import type { StixModifiedTimestamp, XMitrePlatform } from '@/schemas/common/index.js'; +import type { AttackObject } from '@/schemas/sdo/index.js'; import { techniqueSchema } from '@/schemas/sdo/technique.schema.js'; -import type { AttackObject, Technique } from '@/schemas/sdo/index.js'; -import type { TacticImpl } from './tactic.impl.js'; -import type { MitigationImpl } from './mitigation.impl.js'; +import { Validated } from '../common/index.js'; import type { AssetImpl } from './asset.impl.js'; import type { DataComponentImpl } from './data-component.impl.js'; -import type { StixModifiedTimestamp, XMitrePlatform } from '@/schemas/common/index.js'; +import type { MitigationImpl } from './mitigation.impl.js'; +import type { TacticImpl } from './tactic.impl.js'; export class TechniqueImpl extends Validated(techniqueSchema) { // Relationship tracking (mutable, not part of the JSON data) @@ -24,37 +24,37 @@ export class TechniqueImpl extends Validated(techniqueSchema) { } addSubTechnique(subTechnique: TechniqueImpl): void { - if (!this.#subTechniques.includes(subTechnique)) { + if (!this.#subTechniques.some((t) => t.id === subTechnique.id)) { this.#subTechniques.push(subTechnique); } } addTactic(tactic: TacticImpl): void { - if (!this.#tactics.includes(tactic)) { + if (!this.#tactics.some((t) => t.id === tactic.id)) { this.#tactics.push(tactic); } } addMitigation(mitigation: MitigationImpl): void { - if (!this.#mitigations.includes(mitigation)) { + if (!this.#mitigations.some((m) => m.id === mitigation.id)) { this.#mitigations.push(mitigation); } } addRelatedTechnique(technique: TechniqueImpl): void { - if (!this.#relatedTechniques.includes(technique)) { + if (!this.#relatedTechniques.some((t) => t.id === technique.id)) { this.#relatedTechniques.push(technique); } } addTargetAsset(asset: AssetImpl): void { - if (!this.#targetAssets.includes(asset)) { + if (!this.#targetAssets.some((a) => a.id === asset.id)) { this.#targetAssets.push(asset); } } addDetectingDataComponent(dataComponent: DataComponentImpl): void { - if (!this.#detectingDataComponents.includes(dataComponent)) { + if (!this.#detectingDataComponents.some((dc) => dc.id === dataComponent.id)) { this.#detectingDataComponents.push(dataComponent); } } @@ -88,7 +88,6 @@ export class TechniqueImpl extends Validated(techniqueSchema) { return [...this.#detectingDataComponents]; } - // Common functionality getRevokedBy(): AttackObject | undefined { return this.#revokedBy; } @@ -97,6 +96,7 @@ export class TechniqueImpl extends Validated(techniqueSchema) { this.#revokedBy = obj; } + // Business logic methods isDeprecated(): boolean { return this.x_mitre_deprecated ?? false; } @@ -118,40 +118,33 @@ export class TechniqueImpl extends Validated(techniqueSchema) { return attackId ? `${attackId}: ${this.name}` : this.name; } - // Get tactics from kill chain phases getTacticNames(): string[] { return this.kill_chain_phases?.map((phase) => phase.phase_name) ?? []; } - // Get platforms as a comma-separated string getPlatformsString(): string { return this.x_mitre_platforms?.join(', ') ?? ''; } - // Check if technique applies to a specific platform supportsPlatform(platform: string): boolean { return this.x_mitre_platforms?.includes(platform as XMitrePlatform) ?? false; } - // Create a new instance with updated fields with(updates: Partial): TechniqueImpl { const newData = { ...this, ...updates }; return new TechniqueImpl(newData); } - // Create a new instance with updated modified timestamp touch(): TechniqueImpl { return this.with({ modified: new Date().toISOString() as StixModifiedTimestamp, }); } - // Equality check equals(other: TechniqueImpl): boolean { return this.id === other.id && this.modified === other.modified; } - // Check if this version is newer than another isNewerThan(other: TechniqueImpl): boolean { if (this.id !== other.id) { throw new Error('Cannot compare different techniques'); @@ -159,5 +152,3 @@ export class TechniqueImpl extends Validated(techniqueSchema) { return new Date(this.modified) > new Date(other.modified); } } - -export type TechniqueCls = Technique & typeof TechniqueImpl;