diff --git a/.gitignore b/.gitignore index c880ebae..87c25047 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# auto-generated schema documentation +docusaurus/docs/reference/schemas/ + # TypeScript artifacts *.d.ts *.ts.map diff --git a/README.md b/README.md index bbd5bcbd..59052047 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # MITRE ATT&CKยฎ Data Model -The ATT&CK Data Model (ADM) is a TypeScript library that provides a structured way to interact with MITRE ATT&CK datasets. It uses Zod schemas, TypeScript types, and ES6 classes to create a type-safe, object-oriented interface for navigating the ATT&CK data model. This library is designed to parse, validate, and serialize STIX 2.1 formatted content, making it easy to work with ATT&CK-related data in a programmatic and intuitive way. +**A TypeScript library for working with MITRE ATT&CK data using STIX 2.1** -You can browse the ATT&CK schemas in a user-friendly interface at: +The ATT&CK Data Model (ADM) provides a type-safe, object-oriented interface for working with MITRE ATT&CK datasets. +Built on STIX 2.1 compliance, it uses Zod schemas and TypeScript types to ensure data integrity while providing intuitive relationship navigation between ATT&CK objects. -https://mitre-attack.github.io/attack-data-model/. +**[CLICK HERE](https://mitre-attack.github.io/attack-data-model) [1](#footnotes)** to browse the ATT&CK schemas in a user-friendly interface. -This site is dynamically generated from the contents of the `@latest` distribution channel / `main` branch. Please note that we do not currently maintain separate documentation for previous releases. - -## Features +## Key Features - **Type-Safe Data Parsing**: ADM validates STIX 2.1 bundles using Zod schemas, ensuring data model compliance and type safety. - **Easy Relationship Navigation**: Each object instance contains pointers to related objects, simplifying the process of navigating between techniques, tactics, and other ATT&CK elements. @@ -91,9 +90,9 @@ For most users, we recommend: Example of loading the latest ATT&CK data: ```javascript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; -const dataSource = new DataSource({ +const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', version: '17.1', @@ -133,12 +132,12 @@ For additional context about the ATT&CK specification, please refer to the [ATT& Here's an example script that demonstrates how to use the ADM library to load ATT&CK data from the official MITRE ATT&CK GitHub repository: ```typescript -import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; (async () => { - - // Instantiating a DataSource object will validate that the data source is accessible and readable - const dataSource = new DataSource({ + + // Instantiating a DataSourceRegistration object will validate that the data source is accessible and readable + const dataSource = new DataSourceRegistration({ source: 'attack', // Built-in index to retrieve ATT&CK content from the official MITRE ATT&CK STIX 2.1 GitHub repository domain: 'enterprise-attack', version: '15.1', // Omitting 'version' will default to the latest version available in the repository @@ -160,7 +159,7 @@ import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/att // Type hinting is supported for all object properties if (technique.x_mitre_is_subtechnique) { - + // Access related objects with helpful getter methods console.log(technique.getParentTechnique()); } @@ -231,20 +230,24 @@ For more detailed examples, please refer to the [examples](./examples/README.md) ## Compatibility Matrix -Our [COMPATIBILITY.md](./docs/COMPATIBILITY.md) document tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CKยฎ dataset (`mitre-attack/attack-stix-data`). +Our [Compatibility documentation](https://mitre-attack.github.io/attack-data-model/principles/versioning-philosophy) tracks the compatibility between versions of the ATT&CK Data Model (ADM) TypeScript API (`@mitre-attack/attack-data-model`) and versions of the MITRE ATT&CK dataset (`mitre-attack/attack-stix-data`). ## Contributing We welcome contributions! Please see our [CONTRIBUTING.md](./docs/CONTRIBUTING.md) file for details on how to contribute to this project. +## Footnotes + +1 The [schemas site](https://mitre-attack.github.io/attack-data-model) is dynamically generated from the contents of the `@latest` distribution channel / `main` branch. We do not currently maintain separate documentation for previous releases, though we hope to in the future. + ## License This project is licensed under the Apache 2.0 License. -## Notice +## Notice Copyright 2020-2025 The MITRE Corporation. -This project makes use of ATT&CKยฎ +This project makes use of ATT&CK -[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) \ No newline at end of file +[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ffaacecb..c1f69758 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -61,9 +61,21 @@ When submitting a pull request: 5. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 6. Include a description of your changes and why they're necessary. +## Developer Setup + +### Requirements + +- [Node.js](https://nodejs.org/) v18.20 + +### Install dependences + +```bash +npm install +``` + ## Developer Workflow -To maintain code quality and consistency, we use **ESLint**, **Prettier**, and **Husky** as part of the development workflow. Below is an overview of how each tool is configured and how it affects the contribution process: +To maintain code quality and consistency, we use **ESLint**, **Prettier**, and **Husky** as part of the development workflow. These tools are installed and configured by the [Developer Setup](#developer-setup) steps above. Below is an overview of how each tool is configured and how it affects the contribution process: ### ESLint and Prettier Configuration diff --git a/docs/SPEC.md b/docs/SPEC.md index c6049bd2..5f319e67 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,10 +1,10 @@ > [!IMPORTANT] > **Documentation Notice** -> +> > This document is **not the source of truth** for the ATT&CK specification. The authoritative source is the **ATT&CK Data Model (ADM) TypeScript library**. -> -> ๐Ÿ“– **Browse ATT&CK schemas:** https://mitre-attack.github.io/attack-data-model/ -> +> +> ๐Ÿ“– **Browse ATT&CK schemas:** +> > While maintained to the best of our ability, this documentation may drift from the ADM library. If you find discrepancies, please [open a GitHub Issue](https://github.com/mitre-attack/attack-data-model/issues). # The ATT&CK Specification @@ -60,8 +60,7 @@ There are three general ways that ATT&CK extends the STIX 2.1 specification: | `x_mitre_domains` | string[] | Identifies the domains the object is found in. See [domains](#domains) for more information.
Not found on objects of type `relationship`, `identity`, or `marking-definition`. | | `x_mitre_attack_spec_version`1 | string | The version of the ATT&CK specification used by the object. Consuming software can use this field to determine if the data format is supported. Current version is 3.3.0. | - -- New relationship types. Unlike custom object types and extended fields, custom relationship types are **not** prefixed with `x_mitre_`. You can find a full list of relationship types in the [Relationships](#Relationships) section, which also mentions whether the type is a default STIX type. +- New relationship types. Unlike custom object types and extended fields, custom relationship types are **not** prefixed with `x_mitre_`. You can find a full list of relationship types in the [Relationships](#relationships) section, which also mentions whether the type is a default STIX type. Please see also the STIX documentation on [customizing STIX](https://docs.oasis-open.org/cti/stix/v2.0/csprd01/part1-stix-core/stix-v2.0-csprd01-part1-stix-core.html#_Toc476227365). @@ -93,7 +92,7 @@ The `x_mitre_domains` (string array) field identifies the "domain" to which the | `mobile-attack` | Mobile | | `ics-attack` | ATT&CK for ICS | -Most objects in ATT&CK belong in a single technology domain, but an object _may_ be included in multiple domains. +Most objects in ATT&CK belong in a single technology domain, but an object _may_ be included in multiple domains. `x_mitre_domains` is supported on all ATT&CK object types except the following: @@ -127,6 +126,7 @@ ATT&CK IDs are human-readable identifiers commonly used for referencing objects | [Log Source](#log-sources) | `LSxxxx` | **Important limitations:** + - ATT&CK IDs are not guaranteed to be unique (see [Collisions with Technique ATT&CK IDs](#collisions-with-technique-attck-ids)) - Matrices within the same domain share identical ATT&CK IDs - Relationship objects do not have ATT&CK IDs @@ -213,6 +213,7 @@ Sub-techniques are specialized implementations of parent techniques, providing m Procedures describe specific instances of technique implementation by adversaries. Unlike other ATT&CK concepts, procedures are not represented by dedicated STIX objects. Instead, they are modeled as `uses` relationships where the `target_ref` points to a technique (`attack-pattern`). **Procedure relationships:** + - **Source objects:** Groups (`intrusion-set`) or software (`malware`/`tool`) - **Target objects:** Techniques (`attack-pattern`) - **Content:** Procedure details are captured in the relationship's `description` field @@ -250,6 +251,7 @@ Software represents tools and malicious code used by adversaries to accomplish t Data sources and data components define the telemetry and observational data that security teams can use to detect adversary techniques. This hierarchical model provides granular mapping between detection capabilities and techniques. **Structural relationships:** + - Data components are nested within data sources but maintain separate STIX objects - Each data component has exactly one parent data source - Data sources can contain multiple data components @@ -364,6 +366,7 @@ Detection strategies define high-level approaches for detecting specific adversa | `x_mitre_analytic_refs` | string[] | Array of STIX IDs referencing `x-mitre-analytic` objects that implement this detection strategy. | **Key characteristics:** + - Each detection strategy has a one-to-one relationship with a specific ATT&CK technique - Detection strategies typically reference 1-3 analytics (one for each supported platform) - Uses soft relationships (STIX ID references) to analytics for flexibility @@ -497,4 +500,4 @@ Relationship objects frequently include `description` fields that provide contex ### Collections -See our [collections document](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md) for more information about the design and intention of collection objects. \ No newline at end of file +See our [collections document](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md) for more information about the design and intention of collection objects. diff --git a/docs/USAGE.md b/docs/USAGE.md index ef672177..8d5dadba 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -105,68 +105,44 @@ The library provides Zod schemas for all ATT&CK object types, enabling validatio ### Accessing Schemas -Schemas are available under the `schemas` directory. You can import them directly from the package root: +Schemas and their associated TypeScript types are available under the `schemas` directory. You can import them directly from the package root: ```typescript -import { tacticSchema } from '@mitre-attack/attack-data-model'; +import { campaignSchema } from '@mitre-attack/attack-data-model'; +import type { Campaign } from '@mitre-attack/attack-data-model'; ``` -Notably, there are two versions of each ATT&CK type available: - -* One "extensible" schema (denoted by its `extensible` prefix) that returns a `ZodType` -* One "standard" schema that returns a `ZodEffect` - -The extensible schemas (e.g., `extensibleCampaignSchema`) return a `ZodType` by intentionally omitting all Zod refinements. You should use these schemas if you are looking to extend or modify the baseline schema behaviors. - -For example, let's say you wish to augment ATT&CK campaigns with your own custom fieldsโ€”you can do this with the `extensibleCampaignSchema` as follows: - -```typescript -// Using the extensible schema for type definition or extension -import { extensibleCampaignSchema } from '@mitre-attack/attack-data-model'; -const myCustomCampaignSchema = extensibleCampaignSchema.extend({ /* additional fields */ }); -``` +Many of the ATT&CK schemas use Zod refinements. We leverage refinements to execute advanced validation checks (e.g., validating that the first reference in `external_references` contains a valid ATT&CK ID). -This would not work for the "standard" `campaignSchema`. +Unfortunately, in current versions of Zod, if a schema is modified with one of the object methods (`pick`, `omit`, `extend`), the refinements will be discarded. -`campaignSchema` returns a `ZodEffect` by nature of employing Zod refinements. We leverage refinements to execute advanced validation checks (e.g., validating that the first reference in `external_references` contains a valid ATT&CK ID). You can use the refined schemas like you would any other Zod schema, with the added disclaimer that they are less extensible than their aforementioned counterparts: +For example, let's say you wish to augment ATT&CK campaigns with your own custom fields: ```typescript -// Using the refined schema for validation import { campaignSchema } from '@mitre-attack/attack-data-model'; -const validCampaign = campaignSchema.parse(rawCampaignData); +const myCustomCampaignSchema = campaignSchema.extend({ /* additional fields */ }); ``` -And don't worryโ€”you can still use these refinements with your custom schemas. Each ATT&CK refinement is decoupled so they can be used modularly. They are exported as factory functions in the `refinements` sub-package: +`myCustomCampaignSchema` would not be valid, as it is missing the refinements that were originally present in `campaignSchema`. -```typescript -// Step 1 - import the refinements you want to use -import { createFirstAliasRefinement, createCitationsRefinement } from '@mitre-attack/attack-data-model'; +You can still use the original refinements in your custom schemas, it will just take an extra step. Each ATT&CK refinement is decoupled so they can be used modularly. They are exported as factory functions in the `refinements` sub-package: -// Step 2 - initialize the refinements -const validateFirstAlias = createFirstAliasRefinement(); -const validateCitations = createCitationsRefinement(); +```typescript +// Import the original schema, and the refinements you want to use +import { campaignSchema, createFirstAliasRefinement, createCitationsRefinement } from '@mitre-attack/attack-data-model'; -// Step 3 - apply a single refinement that combines the imported refinements -const myCustomCampaignSchema = extensibleCampaignSchema +// Apply a single refinement that combines the imported refinements +const myCustomCampaignSchema = campaignSchema .extend({ /* additional fields */ }) - .superRefine((val, ctx) => { - validateFirstAlias(val, ctx); - validateCitations(val, ctx); + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createCitationsRefinement()(ctx); }); ``` -Notably, all ATT&CK schemas export only one TypeScript type, named in accordance with the refined schema, but inferred from the extensible schema. Since the refinements only add validation rules (rejected values) without changing the shape of valid data, a single type definition is sufficient: - -```typescript -// An extensible schema for customizing or augmenting ATT&CK campaigns -import { extensibleCampaignSchema } from '@mitre-attack/attack-data-model'; - -// An inelastic but fully implemented schema for validating ATT&CK campaigns -import { campaignSchema } from '@mitre-attack/attack-data-model'; +You will have to look in the original schema file, in this case [/src/schemas/sdo/campaign.schema.ts](/src/schemas/sdo/campaign.schema.ts) to see which refinements, if any, should be applied to the ATT&CK schema that you wish to extend. -// One type definition for *all* ATT&CK campaigns (custom or otherwise) -import type { Campaign } from '@mitre-attack/attack-data-model'; -``` +This [GitHub issue](https://github.com/colinhacks/zod/issues/4874) and [pull request](https://github.com/colinhacks/zod/pull/4865) describe the behavior and an upcoming `safeExtend` method that will allow you to extend the ATT&CK schemas without having to reapply the refinements. ### Validating Data diff --git a/docusaurus/.gitignore b/docusaurus/.gitignore index 885b00a7..b2d6de30 100644 --- a/docusaurus/.gitignore +++ b/docusaurus/.gitignore @@ -18,6 +18,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - -docs/*/* -!docs/sdo/stix-bundle.schema.md \ No newline at end of file diff --git a/docusaurus/README.md b/docusaurus/README.md index 475cbec6..4696c0a8 100644 --- a/docusaurus/README.md +++ b/docusaurus/README.md @@ -25,7 +25,7 @@ $ npm install The `generate-docs.sh` script is used to automate the process of converting Zod schemas into Markdown. ```bash -$ npm run autodocs +$ npm run gendocs ``` ## Local Development diff --git a/docusaurus/blog/known-issues.md b/docusaurus/blog/known-issues.md deleted file mode 100644 index de205027..00000000 --- a/docusaurus/blog/known-issues.md +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/docusaurus/docs/contributing/coding-style.mdx b/docusaurus/docs/contributing/coding-style.mdx new file mode 100644 index 00000000..a706720c --- /dev/null +++ b/docusaurus/docs/contributing/coding-style.mdx @@ -0,0 +1,46 @@ +# Coding Style & Linting + +ADM enforces a **single source-of-truth** for style: + +* **ESLint** โ€“ `@eslint/js` + custom rules +* **Prettier** โ€“ code formatting +* **commitlint** โ€“ conventional-commits + +## ESLint + Prettier + +```bash +npm run lint # read-only +npm run format # auto-fix + prettier +``` + +CI will fail if `npm run lint` reports errors or if Prettier formatting changes are uncommitted. + +## Typical workflow + +```bash +git checkout -b feat/my-awesome-change +# โ€ฆcodeโ€ฆ +npm run format +git add . +git commit -m "feat(core): add awesome change" +``` + +The commit message is checked by [commitlint](https://commitlint.js.org). +Prefix your commit with one of the following: + +| Prefix | Description | Example Commit Command | +|----------|------------------------------------------------|----------------------------------------------------------| +| feat | A new feature | `git commit -m "feat: add new schema"` | +| fix | A bug fix | `git commit -m "fix: correct null pointer exception"` | +| docs | Documentation only changes | `git commit -m "docs: update installation instructions"` | +| chore | Routine tasks, maintenance, or tooling | `git commit -m "chore: update dependency versions"` | +| refactor | Code changes that neither fix nor add features | `git commit -m "refactor: simplify token validation"` | +| test | Adding or updating tests | `git commit -m "test: add tests for date parser"` | +| perf | Performance improvements | `git commit -m "perf: optimize query performance"` | + +## TypeScript strictness + +The library is compiled with `"strict": true` and imports must be path-alias aware (`@/โ€ฆ`). +Run `npm run build` to catch any type errors locally. + +--- diff --git a/docusaurus/docs/contributing/dev-setup.mdx b/docusaurus/docs/contributing/dev-setup.mdx new file mode 100644 index 00000000..0b035eb0 --- /dev/null +++ b/docusaurus/docs/contributing/dev-setup.mdx @@ -0,0 +1,35 @@ +# Development Setup + +Follow these steps to get the ADM codebase running on your local machine. + +## Clone and install + +```bash +git clone https://github.com/mitre-attack/attack-data-model.git +cd attack-data-model +npm install +``` + +๐Ÿ’ก The repo is a multi-package workspace (core library + docusaurus site). +Running `npm install` at the root will NOT install the dependencies for building the docs. +Check out the [documentation](docs) contribution guide for more details. + +## Verify TypeScript build + +```bash +npm run build # compiles src โ†’ dist using tsup +npm run test # vitest tests +``` + +## Handy npm scripts + +| Script | Purpose | +|---|---| +| npm run lint | Lints all `src/**` files | +| npm run format | Prettier + ESLint fix | +| npm run test | Run tests | + +You are now ready to hack on the library. +Continue with [Coding Style & Linting](./coding-style) for the mandatory style rules. + +--- diff --git a/docusaurus/docs/contributing/docs.mdx b/docusaurus/docs/contributing/docs.mdx new file mode 100644 index 00000000..b83595bb --- /dev/null +++ b/docusaurus/docs/contributing/docs.mdx @@ -0,0 +1,43 @@ +# Documentation + +This covers how we generate the documentation for the ATT&CK Data Model. + +All of these commands are run from the `docusaurus/` directory, so be sure to run this first. +Otherwise you may be very confused as to why these commands don't exist in the top-level `package.json`! + +```bash +cd docusaurus/ + +# don't forget to install the dependencies that are specific to building the documentation +npm install +``` + +## Auto-generating schema reference files + +The script `generate-docs.sh` introspects **all** `*.schema.ts` files and +emits Markdown to `docusaurus/docs/reference/schemas/*` which is git ignored. + +```bash +npm run gendocs +``` + +## Run the documentation site locally + +Running one of the following commands will enable you to open the site locally at [http://localhost:3000](http://localhost:3000). + +```bash +# opens your default browser automatically +npm start + +# doesn't open your browser +npm run -- --no-open +``` + +## Adding new how-to guides + +Its pretty easy to add a new guide. Basically just do the following: + +1. Create your Markdown file inside `docusaurus/docs//my-guide.md` +2. Update `sidebars.ts` to include it + +--- diff --git a/docusaurus/docs/contributing/index.mdx b/docusaurus/docs/contributing/index.mdx new file mode 100644 index 00000000..32f3b110 --- /dev/null +++ b/docusaurus/docs/contributing/index.mdx @@ -0,0 +1,14 @@ +import DocCardList from '@theme/DocCardList'; + +# Contributing + +**Step-by-step technical instructions for contributors** + +This section explains how to contribute to the ATT&CK Data Model repository. +It complements the high-level `CONTRIBUTING.md` in the root of the repository by focusing on specific developer tasks. + +## Topics + + + +--- diff --git a/docusaurus/docs/contributing/tests.mdx b/docusaurus/docs/contributing/tests.mdx new file mode 100644 index 00000000..166b5e16 --- /dev/null +++ b/docusaurus/docs/contributing/tests.mdx @@ -0,0 +1,42 @@ +# Running and Writing Tests + +The ATT&CK Data Model uses **[Vitest](https://vitest.dev/)** for testing. + +## Execute the full suite + +Running the tests is straightforward enough: + +```bash +npm test +``` + +## Watch mode + +```bash +npm run test:interactive +``` + +Vitest is configured in `vitest.config.ts` with: + +- Node environment +- Global `describe/it/expect` +- Path alias `@` โ†’ `./src` + +## Coverage + +```bash +npm run test:coverage +``` + +Coverage reports are not required for PRs but strongly encouraged for new functionality. + +## Test helpers + +- `test/vitest.setup.ts` โ€“ global test framework setup + +When adding new schemas or refinement logic: + +1. Write **positive** test cases (`schema.parse` passes) +2. Write **negative** test cases that assert specific error messages + +--- diff --git a/docusaurus/docs/how-to-guides/error-handling.mdx b/docusaurus/docs/how-to-guides/error-handling.mdx new file mode 100644 index 00000000..f68f09c2 --- /dev/null +++ b/docusaurus/docs/how-to-guides/error-handling.mdx @@ -0,0 +1,700 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# How to Handle Parsing Errors Gracefully + + + +**Implement robust error handling for production applications** + +When working with ATT&CK data in production, you need comprehensive error handling to manage data source failures, validation errors, and parsing issues. This guide shows you how to build resilient applications that handle errors gracefully while providing meaningful feedback. + +## Problem + +You need to handle various error scenarios: + +- Network failures when loading remote data sources +- Invalid STIX data that fails validation +- Missing or corrupted data files +- Partial parsing failures in relaxed mode +- Timeout issues with large datasets + +## Solution Overview + +Implement layered error handling with specific strategies for different error types, comprehensive logging, and graceful degradation. + +## Step 1: Understanding Error Types + +The ATT&CK Data Model can throw several types of errors: + +```typescript +import { + registerDataSource, + loadDataModel, + DataSource, + DataSourceError, + ValidationError +} from '@mitre-attack/attack-data-model'; +import { ZodError } from 'zod'; + +// Error types you'll encounter: +// 1. Network/IO errors - failed downloads, missing files +// 2. Zod validation errors - schema validation failures +// 3. Data source errors - configuration issues +// 4. Parsing errors - malformed JSON, invalid STIX +// 5. Registration errors - data source registration failures +``` + +## Step 2: Basic Error Handling Pattern + +Create a robust data loading function: + +```typescript +import fs from 'fs'; +import { setTimeout } from 'timers/promises'; + +interface LoadResult { + success: boolean; + dataModel?: AttackDataModel; + errors: string[]; + warnings: string[]; +} + +async function loadAttackDataSafely( + source: 'attack' | 'file' | 'url', + options: any +): Promise { + const result: LoadResult = { + success: false, + errors: [], + warnings: [] + }; + + try { + // Step 1: Create data source with validation + console.log(`๐Ÿ“ก Loading ATT&CK data from ${source}...`); + + const dataSource = new DataSource({ + source, + parsingMode: 'relaxed', // More forgiving for error scenarios + ...options + }); + + // Step 2: Register with timeout + const uuid = await Promise.race([ + registerDataSource(dataSource), + setTimeout(30000).then(() => { + throw new Error('Registration timeout after 30 seconds'); + }) + ]); + + if (!uuid) { + result.errors.push('Failed to register data source - no UUID returned'); + return result; + } + + console.log('โœ… Data source registered successfully'); + + // Step 3: Load data model + const dataModel = loadDataModel(uuid); + + // Step 4: Validate data completeness + const validation = validateDataCompleteness(dataModel); + result.warnings.push(...validation.warnings); + + if (validation.criticalIssues.length > 0) { + result.errors.push(...validation.criticalIssues); + return result; + } + + result.success = true; + result.dataModel = dataModel; + + console.log(`โœ… Successfully loaded ${dataModel.techniques.length} techniques`); + + } catch (error) { + result.errors.push(handleLoadingError(error)); + } + + return result; +} + +function handleLoadingError(error: unknown): string { + if (error instanceof ZodError) { + return `Validation error: ${error.errors.map(e => + `${e.path.join('.')}: ${e.message}` + ).join(', ')}`; + } + + if (error instanceof Error) { + // Network/timeout errors + if (error.message.includes('timeout')) { + return 'Request timeout - data source may be slow or unavailable'; + } + + // File system errors + if (error.message.includes('ENOENT')) { + return 'File not found - check file path and permissions'; + } + + // Network errors + if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) { + return 'Network error - check internet connection and URL'; + } + + return `Error: ${error.message}`; + } + + return `Unknown error: ${String(error)}`; +} +``` + +## Step 3: Data Completeness Validation + +Validate that loaded data meets minimum requirements: + +```typescript +interface DataValidation { + warnings: string[]; + criticalIssues: string[]; + stats: { + techniques: number; + tactics: number; + groups: number; + software: number; + }; +} + +function validateDataCompleteness(dataModel: AttackDataModel): DataValidation { + const result: DataValidation = { + warnings: [], + criticalIssues: [], + stats: { + techniques: dataModel.techniques.length, + tactics: dataModel.tactics.length, + groups: dataModel.groups.length, + software: dataModel.malware.length + dataModel.tools.length + } + }; + + // Critical issues - data is unusable + if (result.stats.techniques === 0) { + result.criticalIssues.push('No techniques loaded - data source may be invalid'); + } + + if (result.stats.tactics === 0) { + result.criticalIssues.push('No tactics loaded - core ATT&CK structure missing'); + } + + // Warnings - data is usable but incomplete + if (result.stats.techniques < 100) { + result.warnings.push(`Only ${result.stats.techniques} techniques loaded - expected 150+`); + } + + if (result.stats.groups < 50) { + result.warnings.push(`Only ${result.stats.groups} groups loaded - expected 100+`); + } + + if (result.stats.software < 200) { + result.warnings.push(`Only ${result.stats.software} software items loaded - expected 400+`); + } + + // Check relationship integrity + const brokenRelationships = checkRelationshipIntegrity(dataModel); + if (brokenRelationships.length > 0) { + result.warnings.push(`${brokenRelationships.length} broken relationships found`); + } + + return result; +} + +function checkRelationshipIntegrity(dataModel: AttackDataModel): string[] { + const issues: string[] = []; + + // Check technique-tactic relationships + dataModel.techniques.forEach(technique => { + try { + const tactics = technique.getTactics(); + if (tactics.length === 0) { + issues.push(`Technique ${technique.name} has no associated tactics`); + } + } catch (error) { + issues.push(`Error getting tactics for technique ${technique.name}: ${error}`); + } + }); + + return issues.slice(0, 10); // Limit to first 10 issues +} +``` + +## Step 4: Retry Logic with Exponential Backoff + +Implement retry logic for transient failures: + +```typescript +interface RetryOptions { + maxAttempts: number; + baseDelay: number; + maxDelay: number; + backoffMultiplier: number; +} + +async function loadWithRetry( + loadFunction: () => Promise, + options: RetryOptions = { + maxAttempts: 3, + baseDelay: 1000, + maxDelay: 10000, + backoffMultiplier: 2 + } +): Promise { + let lastResult: LoadResult | null = null; + + for (let attempt = 1; attempt <= options.maxAttempts; attempt++) { + console.log(`๐Ÿ“ก Attempt ${attempt}/${options.maxAttempts}`); + + try { + const result = await loadFunction(); + + if (result.success) { + if (attempt > 1) { + console.log(`โœ… Succeeded on attempt ${attempt}`); + } + return result; + } + + lastResult = result; + + // Don't retry for certain error types + const nonRetryableErrors = [ + 'File not found', + 'Validation error', + 'Invalid configuration' + ]; + + const hasNonRetryableError = result.errors.some(error => + nonRetryableErrors.some(pattern => error.includes(pattern)) + ); + + if (hasNonRetryableError) { + console.log('โŒ Non-retryable error detected, aborting retries'); + break; + } + + } catch (error) { + lastResult = { + success: false, + errors: [`Attempt ${attempt} failed: ${error}`], + warnings: [] + }; + } + + // Calculate delay with exponential backoff + if (attempt < options.maxAttempts) { + const delay = Math.min( + options.baseDelay * Math.pow(options.backoffMultiplier, attempt - 1), + options.maxDelay + ); + + console.log(`โณ Waiting ${delay}ms before retry...`); + await setTimeout(delay); + } + } + + console.log(`โŒ All ${options.maxAttempts} attempts failed`); + return lastResult || { + success: false, + errors: ['All retry attempts failed'], + warnings: [] + }; +} +``` + +## Step 5: Fallback Data Sources + +Implement fallback strategies when primary sources fail: + +```typescript +interface DataSourceConfig { + primary: DataSource; + fallbacks: DataSource[]; + cacheOptions?: { + enabled: boolean; + ttl: number; // Time to live in seconds + path: string; + }; +} + +class RobustAttackLoader { + private cache = new Map(); + + async loadWithFallbacks(config: DataSourceConfig): Promise { + const sources = [config.primary, ...config.fallbacks]; + let lastResult: LoadResult | null = null; + + // Try cache first if enabled + if (config.cacheOptions?.enabled) { + const cached = this.tryLoadFromCache(config.cacheOptions.path, config.cacheOptions.ttl); + if (cached.success) { + console.log('โœ… Loaded data from cache'); + return cached; + } + } + + // Try each data source + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + const isLastSource = i === sources.length - 1; + + console.log(`๐ŸŽฏ Trying data source ${i + 1}/${sources.length}`); + + const result = await loadWithRetry(async () => { + return await loadAttackDataSafely( + source.source as any, + source + ); + }); + + if (result.success && result.dataModel) { + console.log(`โœ… Successfully loaded from data source ${i + 1}`); + + // Cache successful result + if (config.cacheOptions?.enabled) { + await this.saveToCache(result.dataModel, config.cacheOptions.path); + } + + return result; + } + + lastResult = result; + + if (!isLastSource) { + console.log(`โŒ Data source ${i + 1} failed, trying next...`); + } + } + + console.log('โŒ All data sources failed'); + return lastResult || { + success: false, + errors: ['All data sources failed'], + warnings: [] + }; + } + + private tryLoadFromCache(cachePath: string, ttl: number): LoadResult { + try { + if (!fs.existsSync(cachePath)) { + return { success: false, errors: ['Cache file not found'], warnings: [] }; + } + + const stats = fs.statSync(cachePath); + const age = (Date.now() - stats.mtime.getTime()) / 1000; + + if (age > ttl) { + return { success: false, errors: ['Cache expired'], warnings: [] }; + } + + const cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8')); + + // Reconstruct AttackDataModel from cached data + // (This would need custom deserialization logic) + + return { + success: true, + errors: [], + warnings: [`Using cached data (${Math.round(age)}s old)`] + }; + + } catch (error) { + return { + success: false, + errors: [`Cache load failed: ${error}`], + warnings: [] + }; + } + } + + private async saveToCache(dataModel: AttackDataModel, cachePath: string): Promise { + try { + // Create cache directory if it doesn't exist + const cacheDir = path.dirname(cachePath); + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + // Serialize data model + const cacheData = { + timestamp: Date.now(), + techniques: dataModel.techniques, + tactics: dataModel.tactics, + groups: dataModel.groups, + // ... other collections + }; + + fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2)); + console.log(`๐Ÿ’พ Data cached to ${cachePath}`); + + } catch (error) { + console.warn(`โš ๏ธ Failed to save cache: ${error}`); + } + } +} +``` + +## Step 6: Error Reporting and Monitoring + +Implement comprehensive error reporting: + +```typescript +interface ErrorReport { + timestamp: string; + source: string; + errorType: string; + message: string; + context: any; + stackTrace?: string; +} + +class ErrorReporter { + private errors: ErrorReport[] = []; + + reportError(source: string, error: unknown, context: any = {}): void { + const report: ErrorReport = { + timestamp: new Date().toISOString(), + source, + errorType: error instanceof Error ? error.constructor.name : 'Unknown', + message: error instanceof Error ? error.message : String(error), + context, + stackTrace: error instanceof Error ? error.stack : undefined + }; + + this.errors.push(report); + + // Log to console + console.error(`โŒ [${source}] ${report.message}`, report.context); + + // Send to monitoring service (implement based on your needs) + this.sendToMonitoring(report); + } + + private sendToMonitoring(report: ErrorReport): void { + // Example: Send to external monitoring service + // fetch('/api/errors', { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify(report) + // }); + } + + getErrorSummary(): { [key: string]: number } { + const summary: { [key: string]: number } = {}; + + this.errors.forEach(error => { + summary[error.errorType] = (summary[error.errorType] || 0) + 1; + }); + + return summary; + } + + getRecentErrors(hours: number = 24): ErrorReport[] { + const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000); + return this.errors.filter(error => new Date(error.timestamp) > cutoff); + } +} + +// Global error reporter instance +const errorReporter = new ErrorReporter(); +``` + +## Step 7: Application-Level Error Handling + +Create a complete error handling solution: + +```typescript +interface AppConfig { + dataSources: DataSourceConfig; + errorHandling: { + maxRetries: number; + enableCache: boolean; + cacheTtl: number; + failFast: boolean; + }; + monitoring: { + enabled: boolean; + endpoint?: string; + }; +} + +class AttackDataApp { + private config: AppConfig; + private loader: RobustAttackLoader; + private dataModel?: AttackDataModel; + + constructor(config: AppConfig) { + this.config = config; + this.loader = new RobustAttackLoader(); + } + + async initialize(): Promise { + try { + console.log('๐Ÿš€ Initializing ATT&CK Data Application...'); + + const result = await this.loader.loadWithFallbacks(this.config.dataSources); + + if (!result.success) { + const errorMsg = `Failed to initialize: ${result.errors.join(', ')}`; + errorReporter.reportError('App.initialize', new Error(errorMsg)); + + if (this.config.errorHandling.failFast) { + throw new Error(errorMsg); + } + + console.warn('โš ๏ธ Running with degraded functionality'); + return; + } + + this.dataModel = result.dataModel; + + // Report warnings but continue + if (result.warnings.length > 0) { + result.warnings.forEach(warning => { + console.warn(`โš ๏ธ ${warning}`); + }); + } + + console.log('โœ… Application initialized successfully'); + + } catch (error) { + errorReporter.reportError('App.initialize', error); + throw error; + } + } + + // Safe method calls with error handling + async getTechnique(id: string): Promise { + try { + if (!this.dataModel) { + throw new Error('Application not initialized'); + } + + const technique = this.dataModel.techniques.find(t => + t.external_references[0].external_id === id + ); + + return technique || null; + + } catch (error) { + errorReporter.reportError('App.getTechnique', error, { id }); + return null; + } + } + + // Health check for monitoring + getHealthStatus(): { + status: 'healthy' | 'degraded' | 'unhealthy'; + details: any; + } { + const recentErrors = errorReporter.getRecentErrors(1); + const errorCount = recentErrors.length; + + if (!this.dataModel) { + return { + status: 'unhealthy', + details: { + message: 'Data model not loaded', + errors: errorCount + } + }; + } + + if (errorCount > 10) { + return { + status: 'degraded', + details: { + message: `High error rate: ${errorCount} errors in last hour`, + techniques: this.dataModel.techniques.length + } + }; + } + + return { + status: 'healthy', + details: { + techniques: this.dataModel.techniques.length, + tactics: this.dataModel.tactics.length, + uptime: process.uptime() + } + }; + } +} +``` + +## Step 8: Usage Example + +Put it all together in a production-ready application: + +```typescript +async function main() { + const config: AppConfig = { + dataSources: { + primary: new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' + }), + fallbacks: [ + new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' // More forgiving fallback + }), + new DataSource({ + source: 'file', + file: './backup/enterprise-attack.json', + parsingMode: 'relaxed' + }) + ], + cacheOptions: { + enabled: true, + ttl: 3600, // 1 hour + path: './cache/attack-data.json' + } + }, + errorHandling: { + maxRetries: 3, + enableCache: true, + cacheTtl: 3600, + failFast: false + }, + monitoring: { + enabled: true + } + }; + + const app = new AttackDataApp(config); + + try { + await app.initialize(); + + // Your application logic here + const technique = await app.getTechnique('T1055'); + if (technique) { + console.log(`Found technique: ${technique.name}`); + } + + // Monitor health + setInterval(() => { + const health = app.getHealthStatus(); + console.log(`Health: ${health.status}`, health.details); + }, 60000); // Check every minute + + } catch (error) { + console.error('โŒ Application failed to start:', error); + process.exit(1); + } +} + +main().catch(console.error); +``` + +--- diff --git a/docusaurus/docs/how-to-guides/index.mdx b/docusaurus/docs/how-to-guides/index.mdx new file mode 100644 index 00000000..b341e54b --- /dev/null +++ b/docusaurus/docs/how-to-guides/index.mdx @@ -0,0 +1,16 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; +import DocCardList from '@theme/DocCardList'; + +# How-to Guides + + + +**Problem-oriented solutions for specific ATT&CK Data Model tasks** + +These guides provide direct solutions to common problems you'll encounter when working with the ATT&CK Data Model. Each guide assumes you have basic familiarity with the library and focuses on achieving specific goals quickly and efficiently. + +## Available Guides + + + +--- diff --git a/docusaurus/docs/how-to-guides/manage-data-sources.mdx b/docusaurus/docs/how-to-guides/manage-data-sources.mdx new file mode 100644 index 00000000..df79d617 --- /dev/null +++ b/docusaurus/docs/how-to-guides/manage-data-sources.mdx @@ -0,0 +1,405 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# How to Manage Data Sources + + + +**Switch between different ATT&CK data sources efficiently** + +This guide shows you how to manage multiple ATT&CK data sources, switch between different versions, and work with local files, URLs, and the official repository. + +## Problem Scenarios + +Use this guide when you need to: + +- Switch between different ATT&CK versions for compatibility testing +- Load ATT&CK data from local files instead of the internet +- Fetch data from custom URLs or mirrors +- Manage multiple data sources in a production application +- Cache and reuse data sources efficiently + +## Switch Between ATT&CK Versions + +### Compare Multiple Versions + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +async function compareVersions() { + const versions = ['15.0', '15.1']; + const models: { [version: string]: any } = {}; + + // Load multiple versions + for (const version of versions) { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: version, + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + models[version] = loadDataModel(uuid); + } + + // Compare technique counts + versions.forEach(version => { + const count = models[version].techniques.length; + console.log(`ATT&CK ${version}: ${count} techniques`); + }); +} +``` + +### Use Latest Version + +```typescript +// Omit version to get the latest available +const latestDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + // No version specified = latest + parsingMode: 'relaxed' +}); + +const uuid = await registerDataSource(latestDataSource); +const latestModel = loadDataModel(uuid); +``` + +## Load from Local Files + +### Prepare Local STIX Bundle + +First, save a STIX bundle JSON file locally: + +```bash +# Download ATT&CK data +curl -o enterprise-attack.json https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/enterprise-attack/enterprise-attack.json +``` + +### Load from Local File + +```typescript +import { DataSource } from '@mitre-attack/attack-data-model'; +import path from 'path'; + +async function loadLocalData() { + const filePath = path.resolve('./enterprise-attack.json'); + + const localDataSource = new DataSource({ + source: 'file', + filePath: filePath, + parsingMode: 'relaxed' + }); + + try { + const uuid = await registerDataSource(localDataSource); + const model = loadDataModel(uuid); + + console.log(`Loaded ${model.techniques.length} techniques from local file`); + return model; + + } catch (error) { + console.error('Failed to load local file:', error); + throw error; + } +} +``` + +## Load from Custom URLs + +### Load from URL Source + +```typescript +async function loadFromUrl() { + const customUrl = 'https://your-custom-server.com/attack-data.json'; + + const urlDataSource = new DataSource({ + source: 'url', + url: customUrl, + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(urlDataSource); + const model = loadDataModel(uuid); + + return model; +} +``` + +### Load with Custom Headers + +```typescript +async function loadWithAuth() { + const dataSource = new DataSource({ + source: 'url', + url: 'https://private-server.com/attack-data.json', + requestOptions: { + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'X-Custom-Header': 'value' + } + }, + parsingMode: 'strict' + }); + + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); +} +``` + +## Manage Multiple Data Sources + +### Data Source Registry Pattern + +```typescript +class AttackDataManager { + private dataSources: Map = new Map(); + + async registerSource(name: string, config: any): Promise { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + this.dataSources.set(name, uuid); + return uuid; + } + + getModel(name: string) { + const uuid = this.dataSources.get(name); + if (!uuid) { + throw new Error(`Data source '${name}' not registered`); + } + return loadDataModel(uuid); + } + + async setupCommonSources() { + // Enterprise latest + await this.registerSource('enterprise-latest', { + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + + // Enterprise v15.0 + await this.registerSource('enterprise-v15', { + source: 'attack', + domain: 'enterprise-attack', + version: '15.0', + parsingMode: 'relaxed' + }); + + // Mobile latest + await this.registerSource('mobile-latest', { + source: 'attack', + domain: 'mobile-attack', + parsingMode: 'relaxed' + }); + } +} + +// Usage +const manager = new AttackDataManager(); +await manager.setupCommonSources(); + +const enterpriseModel = manager.getModel('enterprise-latest'); +const mobileModel = manager.getModel('mobile-latest'); +``` + +## Handle Data Source Errors + +### Graceful Fallback Pattern + +```typescript +async function loadWithFallback() { + const fallbackSources = [ + // Try latest first + { + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }, + // Fallback to specific version + { + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }, + // Final fallback to local file + { + source: 'file', + filePath: './backup-enterprise-attack.json', + parsingMode: 'relaxed' + } + ]; + + for (const config of fallbackSources) { + try { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + console.log(`Successfully loaded from source: ${config.source}`); + return model; + + } catch (error) { + console.warn(`Failed to load from ${config.source}:`, error.message); + continue; + } + } + + throw new Error('All data sources failed'); +} +``` + +### Validate Data Source Before Use + +```typescript +async function validateDataSource(config: any): Promise { + try { + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + // Basic validation checks + const hasMinimumTechniques = model.techniques.length > 100; + const hasBasicTactics = model.tactics.length > 5; + const hasGroups = model.groups.length > 0; + + if (hasMinimumTechniques && hasBasicTactics && hasGroups) { + console.log('โœ… Data source validation passed'); + return true; + } else { + console.warn('โš ๏ธ Data source seems incomplete'); + return false; + } + + } catch (error) { + console.error('โŒ Data source validation failed:', error); + return false; + } +} +``` + +## Cache and Performance + +### Implement Simple Caching + +```typescript +class CachedDataManager { + private cache: Map = new Map(); + + private getCacheKey(config: any): string { + return JSON.stringify(config); + } + + async loadData(config: any) { + const cacheKey = this.getCacheKey(config); + + // Check cache first + if (this.cache.has(cacheKey)) { + console.log('๐Ÿ“‹ Loading from cache'); + return this.cache.get(cacheKey); + } + + // Load fresh data + console.log('๐ŸŒ Loading fresh data'); + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + // Cache the result + this.cache.set(cacheKey, model); + + return model; + } + + clearCache() { + this.cache.clear(); + console.log('๐Ÿ—‘๏ธ Cache cleared'); + } +} +``` + +## Production Configuration + +### Environment-Based Data Sources + +```typescript +function getDataSourceConfig(): any { + const environment = process.env.NODE_ENV || 'development'; + + switch (environment) { + case 'production': + return { + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin version in production + parsingMode: 'strict' // Strict validation in production + }; + + case 'staging': + return { + source: 'url', + url: process.env.STAGING_ATTACK_URL, + parsingMode: 'relaxed' + }; + + case 'development': + default: + return { + source: 'file', + filePath: './dev-data/enterprise-attack.json', + parsingMode: 'relaxed' + }; + } +} + +// Usage +const config = getDataSourceConfig(); +const dataSource = new DataSource(config); +``` + +## Monitoring and Logging + +### Add Data Source Monitoring + +```typescript +async function loadWithMonitoring(config: any) { + const startTime = Date.now(); + + try { + console.log('๐Ÿ“ก Starting data source load:', config.source); + + const dataSource = new DataSource(config); + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); + + const loadTime = Date.now() - startTime; + console.log(`โœ… Load completed in ${loadTime}ms`); + console.log(`๐Ÿ“Š Loaded: ${model.techniques.length} techniques, ${model.groups.length} groups`); + + return model; + + } catch (error) { + const loadTime = Date.now() - startTime; + console.error(`โŒ Load failed after ${loadTime}ms:`, error.message); + throw error; + } +} +``` + +## Key Takeaways + +- **Version Management**: Pin specific versions in production, use latest for development +- **Fallback Strategy**: Always have backup data sources for reliability +- **Validation**: Verify data quality after loading, especially with custom sources +- **Caching**: Implement caching for frequently accessed data to improve performance +- **Monitoring**: Log data source performance and success rates for troubleshooting +- **Environment Configuration**: Use different data sources for different deployment environments + +## Related Guides + +- [Handle Parsing Errors Gracefully](./error-handling) - Manage data quality issues +- [Extend Schemas with Custom Fields](./extend-schemas) - Customize data structures +- [Performance Optimization](./performance) - Scale for large datasets + +--- diff --git a/docusaurus/docs/how-to-guides/performance.mdx b/docusaurus/docs/how-to-guides/performance.mdx new file mode 100644 index 00000000..76269737 --- /dev/null +++ b/docusaurus/docs/how-to-guides/performance.mdx @@ -0,0 +1,536 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# How to Optimize Performance + + + +**Scale ATT&CK Data Model for large datasets and production workloads** + +This guide shows you how to optimize performance when working with large ATT&CK datasets, multiple domains, or production applications with high throughput requirements. + +## Problem Scenarios + +Use this guide when you experience: + +- Slow data loading times with large ATT&CK datasets +- High memory usage when working with multiple domains +- Performance bottlenecks in relationship navigation +- Need to optimize for production throughput +- Requirements for concurrent data access + +## Optimize Data Loading + +### Use Relaxed Parsing Mode + +```typescript +// Faster loading - continues on validation errors +const fastDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' // Skip strict validation for speed +}); + +// vs slower but more thorough validation +const strictDataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' // Full validation - slower but safer +}); +``` + +### Parallelize Multiple Domain Loading + +```typescript +async function loadDomainsInParallel() { + const domains = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + // Load all domains simultaneously instead of sequentially + const loadPromises = domains.map(async (domain) => { + const dataSource = new DataSource({ + source: 'attack', + domain: domain.name, + version: '15.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + return { + domain: domain.name, + label: domain.label, + model: loadDataModel(uuid) + }; + }); + + // Wait for all to complete + const results = await Promise.all(loadPromises); + + return results.reduce((acc, result) => { + acc[result.domain] = result.model; + return acc; + }, {} as { [key: string]: any }); +} +``` + +## Memory Optimization + +### Selective Data Loading + +```typescript +class SelectiveAttackDataModel { + private fullModel: any; + + constructor(model: any) { + this.fullModel = model; + } + + // Only load techniques when needed + getTechniques(): any[] { + return this.fullModel.techniques; + } + + // Get lightweight technique summaries + getTechniqueSummaries(): Array<{id: string, name: string, attackId: string}> { + return this.fullModel.techniques.map((t: any) => ({ + id: t.id, + name: t.name, + attackId: t.external_references[0].external_id + })); + } + + // Get specific technique by ID without loading all + getTechniqueById(id: string): any { + return this.fullModel.techniques.find((t: any) => t.id === id); + } + + // Release memory for unused data + clearUnusedData() { + // Remove large description fields if not needed + this.fullModel.techniques.forEach((t: any) => { + if (t.description && t.description.length > 1000) { + t.description = t.description.substring(0, 200) + '...'; + } + }); + } +} +``` + +### Implement Data Streaming + +```typescript +class StreamingAttackProcessor { + private batchSize = 50; + + async processTechniquesInBatches( + model: any, + processor: (batch: any[]) => Promise + ) { + const techniques = model.techniques; + + for (let i = 0; i < techniques.length; i += this.batchSize) { + const batch = techniques.slice(i, i + this.batchSize); + + // Process batch + await processor(batch); + + // Allow event loop to breathe + await new Promise(resolve => setImmediate(resolve)); + + console.log(`Processed ${Math.min(i + this.batchSize, techniques.length)}/${techniques.length} techniques`); + } + } +} + +// Usage +const processor = new StreamingAttackProcessor(); +await processor.processTechniquesInBatches(attackModel, async (batch) => { + // Process each batch of techniques + batch.forEach(technique => { + // Your processing logic here + console.log(`Processing: ${technique.name}`); + }); +}); +``` + +## Optimize Relationship Navigation + +### Cache Relationship Results + +```typescript +class CachedRelationshipNavigator { + private relationshipCache = new Map(); + + getTactics(technique: any): any[] { + const cacheKey = `tactics_${technique.id}`; + + if (this.relationshipCache.has(cacheKey)) { + return this.relationshipCache.get(cacheKey)!; + } + + const tactics = technique.getTactics(); + this.relationshipCache.set(cacheKey, tactics); + + return tactics; + } + + getMitigations(technique: any): any[] { + const cacheKey = `mitigations_${technique.id}`; + + if (this.relationshipCache.has(cacheKey)) { + return this.relationshipCache.get(cacheKey)!; + } + + const mitigations = technique.getMitigations(); + this.relationshipCache.set(cacheKey, mitigations); + + return mitigations; + } + + clearCache() { + this.relationshipCache.clear(); + } + + getCacheStats() { + return { + size: this.relationshipCache.size, + memory: JSON.stringify([...this.relationshipCache.entries()]).length + }; + } +} +``` + +### Pre-compute Common Relationships + +```typescript +class RelationshipIndexer { + private techniqueToTactics = new Map(); + private tacticToTechniques = new Map(); + + buildIndexes(model: any) { + console.log('๐Ÿ”„ Building relationship indexes...'); + const startTime = Date.now(); + + // Index technique โ†’ tactics relationships + model.techniques.forEach((technique: any) => { + const tacticIds = technique.getTactics().map((t: any) => t.id); + this.techniqueToTactics.set(technique.id, tacticIds); + + // Build reverse index + tacticIds.forEach(tacticId => { + if (!this.tacticToTechniques.has(tacticId)) { + this.tacticToTechniques.set(tacticId, []); + } + this.tacticToTechniques.get(tacticId)!.push(technique.id); + }); + }); + + const buildTime = Date.now() - startTime; + console.log(`โœ… Indexes built in ${buildTime}ms`); + console.log(`๐Ÿ“Š Indexed ${this.techniqueToTactics.size} techniques`); + } + + // Fast lookup without method calls + getTacticIdsForTechnique(techniqueId: string): string[] { + return this.techniqueToTactics.get(techniqueId) || []; + } + + getTechniqueIdsForTactic(tacticId: string): string[] { + return this.tacticToTechniques.get(tacticId) || []; + } +} +``` + +## Concurrent Access Patterns + +### Thread-Safe Data Access + +```typescript +import { Worker, isMainThread, parentPort, workerData } from 'worker_threads'; + +class ConcurrentAttackAnalyzer { + async analyzeInWorkers(model: any, numWorkers = 4) { + if (!isMainThread) { + // Worker thread code + this.workerAnalysis(workerData); + return; + } + + // Main thread - distribute work + const techniques = model.techniques; + const chunkSize = Math.ceil(techniques.length / numWorkers); + const workers: Worker[] = []; + const results: any[] = []; + + for (let i = 0; i < numWorkers; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, techniques.length); + const chunk = techniques.slice(start, end); + + const worker = new Worker(__filename, { + workerData: { techniques: chunk, workerId: i } + }); + + workers.push(worker); + + worker.on('message', (result) => { + results.push(result); + + if (results.length === numWorkers) { + // All workers completed + workers.forEach(w => w.terminate()); + this.combineResults(results); + } + }); + } + } + + private workerAnalysis(data: any) { + const { techniques, workerId } = data; + + // Perform analysis on this chunk + const analysis = techniques.map((t: any) => ({ + id: t.id, + name: t.name, + tacticCount: t.getTactics().length, + mitigationCount: t.getMitigations().length + })); + + parentPort?.postMessage({ + workerId, + results: analysis + }); + } + + private combineResults(results: any[]) { + const combined = results.flatMap(r => r.results); + console.log(`๐Ÿ“Š Analyzed ${combined.length} techniques across ${results.length} workers`); + } +} +``` + +## Production Optimization + +### Connection Pooling for Multiple Requests + +```typescript +class AttackDataPool { + private pool: any[] = []; + private maxSize = 10; + private currentSize = 0; + + async getModel(): Promise { + if (this.pool.length > 0) { + return this.pool.pop(); + } + + if (this.currentSize < this.maxSize) { + this.currentSize++; + return await this.createModel(); + } + + // Wait for a model to be returned to the pool + return new Promise((resolve) => { + const checkPool = () => { + if (this.pool.length > 0) { + resolve(this.pool.pop()); + } else { + setTimeout(checkPool, 10); + } + }; + checkPool(); + }); + } + + returnModel(model: any) { + this.pool.push(model); + } + + private async createModel(): Promise { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); + } +} + +// Usage +const pool = new AttackDataPool(); + +async function handleRequest(req: any, res: any) { + const model = await pool.getModel(); + + try { + // Process request using model + const techniques = model.techniques.slice(0, 10); + res.json(techniques); + } finally { + pool.returnModel(model); + } +} +``` + +### Lazy Loading with Proxies + +```typescript +class LazyAttackModel { + private model: any; + private loadedSections = new Set(); + + constructor(model: any) { + this.model = model; + + // Create proxy for lazy loading + return new Proxy(this, { + get(target, prop: string) { + if (prop === 'techniques' && !target.loadedSections.has('techniques')) { + console.log('๐Ÿ”„ Lazy loading techniques...'); + target.loadedSections.add('techniques'); + // Trigger any expensive initialization here + } + + return target.model[prop]; + } + }); + } +} +``` + +## Monitoring Performance + +### Add Performance Metrics + +```typescript +class PerformanceMonitor { + private metrics = new Map(); + + time(operation: string, fn: () => T): T { + const start = performance.now(); + const result = fn(); + const duration = performance.now() - start; + + this.recordMetric(operation, duration); + + return result; + } + + async timeAsync(operation: string, fn: () => Promise): Promise { + const start = performance.now(); + const result = await fn(); + const duration = performance.now() - start; + + this.recordMetric(operation, duration); + + return result; + } + + private recordMetric(operation: string, duration: number) { + if (!this.metrics.has(operation)) { + this.metrics.set(operation, []); + } + this.metrics.get(operation)!.push(duration); + } + + getStats(operation: string) { + const times = this.metrics.get(operation) || []; + + if (times.length === 0) return null; + + const sorted = [...times].sort((a, b) => a - b); + + return { + count: times.length, + average: times.reduce((a, b) => a + b) / times.length, + median: sorted[Math.floor(sorted.length / 2)], + min: sorted[0], + max: sorted[sorted.length - 1], + p95: sorted[Math.floor(sorted.length * 0.95)] + }; + } + + printAllStats() { + console.log('\n๐Ÿ“Š Performance Stats:'); + for (const [operation, _] of this.metrics) { + const stats = this.getStats(operation); + if (stats) { + console.log(`${operation}:`); + console.log(` Average: ${stats.average.toFixed(2)}ms`); + console.log(` Median: ${stats.median.toFixed(2)}ms`); + console.log(` 95th percentile: ${stats.p95.toFixed(2)}ms`); + console.log(` Count: ${stats.count}`); + } + } + } +} + +// Usage +const monitor = new PerformanceMonitor(); + +const model = await monitor.timeAsync('data-loading', async () => { + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + parsingMode: 'relaxed' + }); + const uuid = await registerDataSource(dataSource); + return loadDataModel(uuid); +}); + +const tactics = monitor.time('get-tactics', () => { + return model.techniques[0].getTactics(); +}); + +monitor.printAllStats(); +``` + +## Configuration for Scale + +### Production Environment Variables + +```bash +# .env.production +NODE_ENV=production +ATTACK_PARSING_MODE=strict +ATTACK_VERSION=15.1 +ATTACK_CACHE_SIZE=100 +ATTACK_WORKER_THREADS=4 +ATTACK_MEMORY_LIMIT=2048 +``` + +```typescript +// Production configuration +const config = { + parsingMode: process.env.ATTACK_PARSING_MODE || 'relaxed', + version: process.env.ATTACK_VERSION || '15.1', + cacheSize: parseInt(process.env.ATTACK_CACHE_SIZE || '10'), + workerThreads: parseInt(process.env.ATTACK_WORKER_THREADS || '2'), + memoryLimit: parseInt(process.env.ATTACK_MEMORY_LIMIT || '1024') +}; +``` + +## Key Performance Tips + +1. **Use Relaxed Parsing** in development, strict in production +2. **Load Domains in Parallel** when you need multiple domains +3. **Cache Relationship Results** for frequently accessed data +4. **Pre-compute Indexes** for complex relationship queries +5. **Implement Lazy Loading** for large datasets +6. **Monitor Performance** with metrics and logging +7. **Use Worker Threads** for CPU-intensive analysis +8. **Pool Data Models** in high-throughput applications + +## Related Guides + +- [Manage Data Sources](./manage-data-sources) - Efficient data source management +- [Handle Parsing Errors](./error-handling) - Deal with data quality issues +- [Reference: Configuration](../reference/configuration) - All configuration options + +--- diff --git a/docusaurus/docs/how-to-guides/validate-bundles.mdx b/docusaurus/docs/how-to-guides/validate-bundles.mdx new file mode 100644 index 00000000..6d15f8c4 --- /dev/null +++ b/docusaurus/docs/how-to-guides/validate-bundles.mdx @@ -0,0 +1,409 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# How to Validate Custom STIX Bundles + + + +**Ensure your custom ATT&CK data meets specification requirements** + +When working with custom STIX 2.1 bundles containing ATT&CK data, you need to validate that they conform to both the STIX specification and ATT&CK schema requirements. This guide shows you how to implement comprehensive validation for your custom data. + +## Problem + +You have custom STIX 2.1 bundles containing ATT&CK objects and need to: + +- Verify they meet STIX 2.1 specification requirements +- Ensure ATT&CK-specific validation rules are satisfied +- Handle validation errors appropriately +- Integrate validation into your data processing pipeline + +## Solution Overview + +Use the ATT&CK Data Model's validation system with proper error handling to validate custom bundles before processing them. + +## Step 1: Basic Bundle Validation + +Create a validation function for STIX bundles: + +```typescript +import { + registerDataSource, + loadDataModel, + DataSource, + stixBundleSchema +} from '@mitre-attack/attack-data-model'; +import { z } from 'zod'; +import fs from 'fs'; + +async function validateCustomBundle(filePath: string): Promise { + try { + // Read the bundle file + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Basic STIX bundle validation + const validatedBundle = stixBundleSchema.parse(bundleData); + + console.log('โœ… Bundle structure is valid'); + console.log(`๐Ÿ“ฆ Bundle contains ${validatedBundle.objects.length} objects`); + + return true; + + } catch (error) { + if (error instanceof z.ZodError) { + console.error('โŒ Bundle validation failed:'); + error.errors.forEach(err => { + console.error(` - ${err.path.join('.')}: ${err.message}`); + }); + } else { + console.error('โŒ Error reading bundle:', error); + } + return false; + } +} +``` + +## Step 2: ATT&CK Object Validation + +Validate individual ATT&CK objects within the bundle: + +```typescript +import { + techniqueSchema, + tacticSchema, + groupSchema, + malwareSchema, + toolSchema, + campaignSchema +} from '@mitre-attack/attack-data-model'; + +interface ValidationResult { + isValid: boolean; + errors: string[]; + objectCounts: { [key: string]: number }; +} + +function validateAttackObjects(bundle: any): ValidationResult { + const result: ValidationResult = { + isValid: true, + errors: [], + objectCounts: {} + }; + + // Schema mapping for ATT&CK objects + const schemaMap: { [key: string]: z.ZodSchema } = { + 'attack-pattern': techniqueSchema, + 'x-mitre-tactic': tacticSchema, + 'intrusion-set': groupSchema, + 'malware': malwareSchema, + 'tool': toolSchema, + 'campaign': campaignSchema + }; + + bundle.objects.forEach((obj: any, index: number) => { + const objType = obj.type; + + // Count objects by type + result.objectCounts[objType] = (result.objectCounts[objType] || 0) + 1; + + // Validate ATT&CK objects + if (schemaMap[objType]) { + try { + schemaMap[objType].parse(obj); + } catch (error) { + result.isValid = false; + if (error instanceof z.ZodError) { + error.errors.forEach(err => { + result.errors.push( + `Object ${index} (${objType}): ${err.path.join('.')}: ${err.message}` + ); + }); + } + } + } + }); + + return result; +} +``` + +## Step 3: Comprehensive Bundle Validator + +Combine validation steps into a comprehensive validator: + +```typescript +async function comprehensiveValidation(filePath: string): Promise { + console.log(`๐Ÿ” Validating bundle: ${filePath}\n`); + + try { + // Step 1: Read and parse + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Step 2: Validate bundle structure + console.log('๐Ÿ“‹ Validating STIX bundle structure...'); + const validatedBundle = stixBundleSchema.parse(bundleData); + console.log('โœ… Bundle structure is valid\n'); + + // Step 3: Validate ATT&CK objects + console.log('๐ŸŽฏ Validating ATT&CK objects...'); + const objectValidation = validateAttackObjects(validatedBundle); + + // Display object counts + console.log('๐Ÿ“Š Object counts:'); + Object.entries(objectValidation.objectCounts).forEach(([type, count]) => { + console.log(` ${type}: ${count}`); + }); + console.log(''); + + // Display validation results + if (objectValidation.isValid) { + console.log('โœ… All ATT&CK objects are valid'); + } else { + console.log('โŒ ATT&CK object validation failed:'); + objectValidation.errors.forEach(error => { + console.log(` - ${error}`); + }); + } + + // Step 4: Test loading with Data Model + console.log('\n๐Ÿ”„ Testing bundle loading...'); + const dataSource = new DataSource({ + source: 'file', + file: filePath, + parsingMode: 'strict' // Use strict mode for validation + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + const attackDataModel = loadDataModel(uuid); + console.log('โœ… Bundle loads successfully in ATT&CK Data Model'); + console.log(`๐Ÿ“ˆ Loaded ${attackDataModel.techniques.length} techniques`); + console.log(`๐Ÿ“ˆ Loaded ${attackDataModel.tactics.length} tactics`); + } + + } catch (error) { + console.error('โŒ Validation failed:', error); + + if (error instanceof z.ZodError) { + console.error('\n๐Ÿ“ Detailed errors:'); + error.errors.forEach(err => { + console.error(` - ${err.path.join('.')}: ${err.message}`); + }); + } + } +} +``` + +## Step 4: Batch Validation + +Validate multiple bundles at once: + +```typescript +async function validateMultipleBundles(bundlePaths: string[]): Promise { + console.log(`๐Ÿ”„ Validating ${bundlePaths.length} bundles...\n`); + + const results = await Promise.allSettled( + bundlePaths.map(async (path) => { + try { + await comprehensiveValidation(path); + return { path, success: true }; + } catch (error) { + return { path, success: false, error }; + } + }) + ); + + // Summary + const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length; + const failed = results.length - successful; + + console.log('\n๐Ÿ“Š Validation Summary:'); + console.log(`โœ… Successful: ${successful}`); + console.log(`โŒ Failed: ${failed}`); + + if (failed > 0) { + console.log('\nโŒ Failed bundles:'); + results.forEach(result => { + if (result.status === 'fulfilled' && !result.value.success) { + console.log(` - ${result.value.path}`); + } + }); + } +} +``` + +## Step 5: Integration into Your Pipeline + +Create a reusable validation utility: + +```typescript +export interface BundleValidationOptions { + strictMode?: boolean; + validateRelationships?: boolean; + allowedObjectTypes?: string[]; +} + +export class BundleValidator { + private options: BundleValidationOptions; + + constructor(options: BundleValidationOptions = {}) { + this.options = { + strictMode: true, + validateRelationships: true, + ...options + }; + } + + async validate(filePath: string): Promise { + try { + // Implementation combining all validation steps + const bundleContent = fs.readFileSync(filePath, 'utf8'); + const bundleData = JSON.parse(bundleContent); + + // Validate with appropriate settings + const bundleValidation = stixBundleSchema.parse(bundleData); + const objectValidation = validateAttackObjects(bundleValidation); + + if (this.options.validateRelationships) { + // Test loading to validate relationships + const dataSource = new DataSource({ + source: 'file', + file: filePath, + parsingMode: this.options.strictMode ? 'strict' : 'relaxed' + }); + + await registerDataSource(dataSource); + } + + return { + isValid: objectValidation.isValid, + errors: objectValidation.errors, + objectCounts: objectValidation.objectCounts + }; + + } catch (error) { + return { + isValid: false, + errors: [`Validation failed: ${error}`], + objectCounts: {} + }; + } + } +} + +// Usage example +const validator = new BundleValidator({ + strictMode: true, + validateRelationships: true +}); + +const result = await validator.validate('my-custom-bundle.json'); +if (!result.isValid) { + console.error('Validation failed:', result.errors); +} +``` + +## Common Validation Issues + +### Missing Required Fields + +```typescript +// โŒ Invalid technique - missing required fields +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "name": "My Custom Technique" + // Missing: spec_version, created, modified, description, etc. +} + +// โœ… Valid technique with all required fields +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "spec_version": "2.1", + "created": "2023-01-01T00:00:00.000Z", + "modified": "2023-01-01T00:00:00.000Z", + "name": "My Custom Technique", + "description": "Description of the technique", + "x_mitre_attack_spec_version": "3.3.0", + "x_mitre_version": "1.0" +} +``` + +### Invalid ATT&CK IDs + +```typescript +// Validate ATT&CK ID format +function validateAttackId(id: string, objectType: string): boolean { + const patterns: { [key: string]: RegExp } = { + 'attack-pattern': /^T\d{4}(\.\d{3})?$/, // T1234 or T1234.001 + 'x-mitre-tactic': /^TA\d{4}$/, // TA0001 + 'intrusion-set': /^G\d{4}$/, // G0001 + 'malware': /^S\d{4}$/, // S0001 + 'tool': /^S\d{4}$/ // S0001 + }; + + const pattern = patterns[objectType]; + return pattern ? pattern.test(id) : true; +} +``` + +## Performance Considerations + +For large bundles, implement streaming validation: + +```typescript +import { Transform } from 'stream'; +import { parse } from 'stream-json'; +import StreamValues from 'stream-json/streamers/StreamValues'; + +async function validateLargeBundle(filePath: string): Promise { + const pipeline = fs.createReadStream(filePath) + .pipe(parse()) + .pipe(StreamValues.withParser()) + .pipe(new Transform({ + objectMode: true, + transform(chunk, encoding, callback) { + try { + // Validate individual objects + const obj = chunk.value; + // Perform validation on obj + callback(null, chunk); + } catch (error) { + callback(error); + } + } + })); + + return new Promise((resolve, reject) => { + pipeline.on('end', resolve); + pipeline.on('error', reject); + }); +} +``` + +## Integration with CI/CD + +Add validation to your build pipeline: + +```bash +#!/bin/bash +# validate-bundles.sh + +echo "Validating ATT&CK bundles..." + +for bundle in data/*.json; do + echo "Validating $bundle..." + npx tsx validate-bundle.ts "$bundle" + + if [ $? -ne 0 ]; then + echo "โŒ Validation failed for $bundle" + exit 1 + fi +done + +echo "โœ… All bundles validated successfully" +``` + +--- diff --git a/docusaurus/docs/index.mdx b/docusaurus/docs/index.mdx new file mode 100644 index 00000000..e1199a01 --- /dev/null +++ b/docusaurus/docs/index.mdx @@ -0,0 +1,79 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# ATT&CK Data Model Documentation + + + +*A TypeScript library for working with MITRE ATT&CK data using STIX 2.1 bundles* + +Welcome to the documentation for the ATT&CK Data Model library. +This documentation aims to provide you with exactly the right type of information for your needs, whether you are a beginner or a seasoned pro. + +## Quick Start + +```bash +# Install the library +npm install @mitre-attack/attack-data-model + +# Import and use +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; +``` + +## What is the ATT&CK Data Model? + +The ATT&CK Data Model (ADM) is a TypeScript library that strives to be: + +- **Type-Safe**: Full TypeScript support with compile-time validation +- **STIX 2.1 Compliant**: Standards-compliant +- **Relationship Navigation**: Intuitive methods for exploring connections +- **Multiple Data Sources**: Official repository, local files, URLs, TAXII + +## Zod Schema Foundation + +The ATT&CK Data Model leverages **[Zod](https://zod.dev/)**, a TypeScript-first schema validation library, to ensure data integrity and enable powerful developer experiences: + +### Key Benefits + +- **Data Validation**: Parse and validate ATT&CK data as strongly-typed TypeScript objects +- **Error Prevention**: Catch data inconsistencies at compile-time and runtime +- **Maintainability**: Single source of truth for data structures reduces maintenance overhead + +### Schema-Driven Development + +```typescript +import { techniqueSchema } from '@mitre-attack/attack-data-model'; + +// Automatic validation and type inference +const technique = techniqueSchema.parse(rawAttackData); +console.log(technique.name); // TypeScript knows this is a string +``` + +## Known Compliance Status + +**Current State**: The ATT&CK knowledge base does not fully conform to all defined schemas due to evolving data quality standards. + +**Our Approach**: + +- **Continuous Improvement**: Known discrepancies are actively tracked and addressed +- **Flexibility**: Library supports both `strict` and `relaxed` parsing modes +- **Transparency**: Validation errors are clearly documented and reported + +**For Users**: Use `relaxed` mode for production workflows while we work toward full schema compliance. + +## Current Version Information + +- **Library Version**: Latest release from npm +- **ATT&CK Specification**: 3.3.0 +- **STIX Version**: 2.1 +- **Node.js**: 20.0.0+ +- **TypeScript**: 4.5.0+ + +## Community and Support + +- **Browse these docs** for comprehensive guidance on the ATT&CK Data Model library +- **[Report issues](https://github.com/mitre-attack/attack-data-model/issues)** on GitHub +- **[Contact](https://attack.mitre.org/resources/engage-with-attack/contact/)** the MITRE ATT&CK team for questions about ATT&CK itself + +--- + +Ready to get started? Check out the links in the sidebar to dive right in! diff --git a/docusaurus/docs/overview.md b/docusaurus/docs/overview.md deleted file mode 100644 index c0b30b5f..00000000 --- a/docusaurus/docs/overview.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - -// automate the overview summary here \ No newline at end of file diff --git a/docusaurus/docs/overview.mdx b/docusaurus/docs/overview.mdx new file mode 100644 index 00000000..fc7f2871 --- /dev/null +++ b/docusaurus/docs/overview.mdx @@ -0,0 +1,234 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Overview + + + +**A comprehensive TypeScript library for MITRE ATT&CK data** + +This page provides a high-level overview of the ATT&CK Data Model library architecture, its core concepts, and how all the pieces fit together. + +## What is the ATT&CK Data Model? + +The ATT&CK Data Model (ADM) is a TypeScript library that provides type-safe, programmatic access to MITRE ATT&CK datasets. It bridges the gap between raw STIX 2.1 data and developer-friendly TypeScript objects. + +### Core Value Proposition + +- **Type Safety**: Full TypeScript support prevents runtime errors +- **STIX 2.1 Compliance**: Maintains standards compliance while adding usability +- **Relationship Navigation**: Intuitive methods for exploring ATT&CK connections +- **Multi-Domain Support**: Works with Enterprise, Mobile, and ICS domains +- **Performance Optimized**: Designed for both memory efficiency and query speed + +## Architecture Overview + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Data Sources โ”‚ โ”‚ Validation โ”‚ โ”‚ Object Model โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ€ข GitHub Repository โ”‚โ”€โ”€โ”€โ–ถโ”‚ โ€ข Zod Schemas โ”‚โ”€โ”€โ”€โ–ถโ”‚ โ€ข ES6 Classes โ”‚ +โ”‚ โ€ข Local Files โ”‚ โ”‚ โ€ข STIX 2.1 Spec โ”‚ โ”‚ โ€ข Type Definitions โ”‚ +โ”‚ โ€ข Custom URLs โ”‚ โ”‚ โ€ข ATT&CK Rules โ”‚ โ”‚ โ€ข Relationship APIs โ”‚ +โ”‚ โ€ข TAXII Servers โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ AttackDataModel โ”‚ + โ”‚ โ”‚ + โ”‚ โ€ข Central Hub โ”‚ + โ”‚ โ€ข Collections โ”‚ + โ”‚ โ€ข Relationships โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Core Components + +### 1. Data Sources (`src/data-sources/`) + +Handles loading ATT&CK data from various sources: + +- **attack**: Official MITRE ATT&CK GitHub repository +- **file**: Local JSON files containing STIX 2.1 bundles +- **url**: Remote URLs serving STIX 2.1 content +- **taxii**: TAXII 2.1 servers (planned) + +### 2. Validation Layer (`src/schemas/`) + +Ensures data integrity through Zod schemas: + +- **STIX 2.1 Base**: Foundation schemas following STIX specification +- **ATT&CK Extensions**: Custom fields and relationships specific to ATT&CK +- **Refinements**: Advanced validation rules for ATT&CK-specific constraints + +### 3. Object Model (`src/classes/`) + +Provides developer-friendly interfaces: + +- **Implementation Classes**: ES6 classes for each ATT&CK object type +- **Relationship Methods**: Navigate connections between objects intuitively +- **Type Safety**: Full TypeScript support with compile-time checking + +### 4. AttackDataModel (`src/classes/attack-data-model.ts`) + +Central hub containing all ATT&CK objects with automatic relationship mapping. + +## Object Type Hierarchy + +### STIX Domain Objects (SDOs) + +Core ATT&CK concepts represented as STIX objects: + +| ATT&CK Concept | STIX Type | Custom? | Description | +|----------------|-----------|---------|-------------| +| **Technique** | `attack-pattern` | No | Methods adversaries use to achieve goals | +| **Tactic** | `x-mitre-tactic` | Yes | Adversary tactical objectives | +| **Group** | `intrusion-set` | No | Adversary organizations | +| **Software** | `malware`/`tool` | No | Adversary tools and malware | +| **Mitigation** | `course-of-action` | No | Defensive countermeasures | +| **Campaign** | `campaign` | No | Sets of adversary activities | +| **Data Source** | `x-mitre-data-source` | Yes | Detection data categories | +| **Matrix** | `x-mitre-matrix` | Yes | Organizational structure | + +### STIX Relationship Objects (SROs) + +Connections between ATT&CK objects: + +- **uses**: Groups/campaigns/software using techniques +- **mitigates**: Mitigations addressing techniques +- **subtechnique-of**: Sub-technique to parent relationships +- **detects**: Data components detecting techniques + +## Data Flow + +### Registration Process + +```typescript +// 1. Create data source configuration +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +// 2. Register and validate data +const uuid = await registerDataSource(dataSource); + +// 3. Load typed data model +const attackDataModel = loadDataModel(uuid); +``` + +### Validation Pipeline + +1. **Raw STIX Data**: JSON from data source +2. **Schema Validation**: Zod schemas ensure STIX compliance +3. **Refinement Checks**: ATT&CK-specific validation rules +4. **Object Creation**: Conversion to TypeScript classes +5. **Relationship Mapping**: Automatic linking between objects + +### Relationship Navigation + +```typescript +const technique = attackDataModel.techniques[0]; + +// Navigate relationships using intuitive methods +const tactics = technique.getTactics(); // Associated tactics +const groups = technique.getGroups(); // Groups using this technique +const mitigations = technique.getMitigations(); // Available mitigations +const parent = technique.getParentTechnique(); // Parent (if sub-technique) +``` + +## Multi-Domain Support + +The library supports all three ATT&CK domains: + +### Enterprise Domain (`enterprise-attack`) + +- Traditional IT environments +- Most comprehensive technique coverage +- Extensive group and software attribution + +### Mobile Domain (`mobile-attack`) + +- Mobile device threats +- Platform-specific techniques +- App store and mobile-specific tactics + +### ICS Domain (`ics-attack`) + +- Industrial Control Systems +- Operational Technology focus +- Critical infrastructure contexts + +## Extensibility + +### Custom Fields + +Extend ATT&CK objects with custom properties while maintaining compliance: + +```typescript +const customTechniqueSchema = techniqueSchema.extend({ + custom_severity: z.number().optional(), + custom_tags: z.array(z.string()).optional() +}); +``` + +### Custom Refinements + +Apply additional validation rules: + +```typescript +const refinedSchema = customTechniqueSchema.check((data) => { + // Custom validation logic + return data.custom_severity <= 10; +}); +``` + +## Performance Characteristics + +### Memory Usage + +- **Efficient Object Storage**: Optimized class instances +- **Lazy Relationship Loading**: Relationships computed on demand +- **Configurable Caching**: Balance memory vs. performance + +### Query Performance + +- **Direct Property Access**: No query parsing overhead +- **Pre-computed Relationships**: Fast navigation between objects +- **TypeScript Optimization**: Compile-time optimizations + +## Standards Compliance + +### STIX 2.1 Foundation + +- Full compliance with STIX 2.1 specification +- Support for all STIX Domain and Relationship Objects +- Extensible through STIX custom properties pattern + +### ATT&CK Specification + +- Implements ATT&CK Specification 3.3.0 +- Support for all ATT&CK object types and relationships +- Backwards compatibility with previous versions + +## Integration Patterns + +### Application Integration + +- Import as npm package +- TypeScript-first development experience +- Works with any JavaScript framework + +### Data Pipeline Integration + +- Stream processing support +- Batch analysis capabilities +- Export to various formats + +### Security Tool Integration + +- SIEM integration patterns +- Threat hunting query generation +- Detection rule development + +--- diff --git a/docusaurus/docs/principles/attack-specification-overview.mdx b/docusaurus/docs/principles/attack-specification-overview.mdx new file mode 100644 index 00000000..1b4b46a8 --- /dev/null +++ b/docusaurus/docs/principles/attack-specification-overview.mdx @@ -0,0 +1,275 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# ATT&CK Specification Overview + + + +**Understanding the structure and purpose of the ATT&CK specification** + +The ATT&CK specification defines the formal structure, semantics, and constraints that govern how MITRE ATT&CK data is represented, validated, and consumed. Understanding the specification's design philosophy and architecture is crucial for working effectively with ATT&CK data and building robust applications that leverage the ATT&CK framework. + +## What is the ATT&CK Specification? + +The ATT&CK specification is a codified expression of the concepts outlined in the [MITRE ATT&CK Philosophy Paper](https://attack.mitre.org/docs/ATTACK_Design_and_Philosophy_March_2020.pdf), which is in turn built atop the [STIX 2.1 specification](https://oasis-open.github.io/cti-documentation/resources#stix-21-specification). Rather than creating a completely new data format, the specification extends STIX 2.1 with ATT&CK-specific object types, properties, and validation rules. + +### The Specification Hierarchy + +``` +STIX 2.1 Specification (OASIS Standard) + โ”‚ + โ”œโ”€โ”€ Base STIX objects and properties + โ”œโ”€โ”€ Standard relationship patterns + โ”œโ”€โ”€ Extension mechanisms + โ”‚ + โ””โ”€โ”€ ATT&CK Specification Extensions + โ”‚ + โ”œโ”€โ”€ Custom object types (x-mitre-*) + โ”œโ”€โ”€ Custom properties (x_mitre_*) + โ”œโ”€โ”€ Custom relationship types + โ””โ”€โ”€ ATT&CK business rules and validation +``` + +**Key insight**: The ATT&CK specification is not a replacement for STIXโ€”it's a disciplined extension that maintains full STIX compliance while adding the semantic richness needed to represent adversary tactics, techniques, and procedures. + +## Design Philosophy + +### Standards-First Approach + +The specification prioritizes standards compliance over custom optimization. +This design choice reflects several strategic decisions: + +#### Interoperability Over Convenience + +By extending rather than replacing STIX, ATT&CK data can be processed by existing STIX-compliant tools and workflows. + +#### Long-term Sustainability Over Short-term Simplicity + +Standards provide governance, stability, and community support that proprietary formats cannot match. The specification accepts some complexity in exchange for long-term viability. + +#### Community Ecosystem Over Isolated Solutions + +STIX compliance enables ATT&CK data to integrate naturally with threat intelligence platforms, security orchestration systems, and analyst tools that already support STIX. + +### Semantic Richness Through Extensions + +ATT&CK concepts require more specificity than generic STIX objects provide. The specification achieves this through disciplined extensions: + +#### Custom Object Types + +Objects like `x-mitre-tactic`, `x-mitre-matrix`, and `x-mitre-data-source` represent ATT&CK concepts that don't map cleanly to existing STIX types. + +#### Custom Properties + +Properties like `x_mitre_platforms`, `x_mitre_is_subtechnique`, and `x_mitre_attack_spec_version` add domain-specific metadata to standard STIX objects. + +#### Custom Relationships + +Relationship types like `subtechnique-of`, `detects`, and `mitigates` capture ATT&CK-specific associations between objects. + +## Architectural Implications + +### Object Identity Strategy + +The specification employs a dual-identity approach that balances STIX requirements with ATT&CK usability: + +#### Primary Identifiers (STIX IDs) + +Every object has a globally unique STIX ID (e.g., `attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298`) that ensures referential integrity across systems. + +#### Human-Readable Identifiers (ATT&CK IDs) + +Most objects also have human-readable ATT&CK IDs (e.g., `T1055`) stored as external references for documentation and communication. + +**Design rationale**: This dual approach supports both machine processing (via STIX IDs) and human comprehension (via ATT&CK IDs) without compromising either use case. + +### Relationship Architecture + +The specification models relationships as explicit STIX relationship objects rather than embedded references: + +```json +// โŒ Embedded reference approach +{ + "type": "attack-pattern", + "tactics": ["TA0001", "TA0002"] +} + +// โœ… STIX relationship approach +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "attack-pattern--12345...", + "target_ref": "x-mitre-tactic--67890..." +} +``` + +**Benefits**: + +- Relationships can carry their own metadata (descriptions, relationship type) +- Bidirectional navigation is naturally supported +- Relationship evolution doesn't require object schema changes + +### Extension Mechanisms + +The specification defines three primary extension patterns: + +#### 1. Custom Object Types + +```json +{ + "type": "x-mitre-tactic", + "id": "x-mitre-tactic--78b23412-0651-46d7-a540-170a1ce8bd5a", + "x_mitre_shortname": "execution" +} +``` + +Custom types follow the STIX Domain Object pattern but represent concepts unique to ATT&CK. + +#### 2. Custom Properties on Standard Objects + +```json +{ + "type": "attack-pattern", + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "x_mitre_platforms": ["Windows", "Linux"], + "x_mitre_is_subtechnique": false +} +``` + +Standard STIX objects are extended with ATT&CK-specific properties using the `x_mitre_` namespace. + +#### 3. Custom Relationship Types + +```json +{ + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern-- ", + "target_ref": "attack-pattern-- " +} +``` + +New relationship types capture ATT&CK-specific associations not covered by standard STIX relationships. + +## Validation Philosophy + +### Layered Validation Strategy + +The specification implements validation through multiple layers: + +#### 1. STIX Base Compliance + +All objects must conform to STIX 2.1 structural requirements (required fields, data types, etc.). + +#### 2. ATT&CK Extension Validation + +Custom properties and objects must conform to ATT&CK-specific schemas and constraints. + +#### 3. ATT&CK Business Rules + +Domain-specific rules enforce requirements like ATT&CK ID formats, tactic associations, and relationship constraints. + +#### 4. Cross-Object Validation + +References between objects must be valid and consistent (e.g., subtechnique parents must exist). + +### Strict vs. Relaxed Modes + +The specification supports two validation philosophies: + +#### Strict Mode + +**Philosophy**: "Data integrity is paramountโ€”invalid data must be rejected completely" + +- All objects must pass all validation layers +- Processing aborts on any validation failure +- Appropriate for production systems requiring data quality guarantees + +#### Relaxed Mode + +**Philosophy**: "Partial data is better than no dataโ€”log errors but continue processing" + +- Invalid objects are logged but not rejected +- Processing continues with valid objects only +- Appropriate for research environments and data migration scenarios + +## Evolution Patterns + +### Version Management Strategy + +The specification uses three distinct versioning dimensions: + +#### STIX Version (`spec_version`) + +Tracks compliance with STIX specification versions (managed by OASIS). + +#### ATT&CK Specification Version (`x_mitre_attack_spec_version`) + +Tracks compatibility with ATT&CK specification extensions (managed by MITRE). + +#### Object Version (`x_mitre_version`) + +Tracks semantic changes to individual object content (managed by MITRE). + +**Design rationale**: This multi-dimensional approach allows independent evolution of STIX standards, ATT&CK specification features, and individual object content. + +### Backward Compatibility Strategy + +The specification maintains backward compatibility through: + +#### Additive Changes + +New fields and object types are added without removing existing ones. + +#### Deprecation Warnings + +Obsolete features are marked deprecated before removal, providing migration time. + +#### Version-Aware Processing + +Consuming applications can adapt behavior based on specification version. + +#### Legacy Support + +Deprecated features remain functional until formal removal in major version updates. + +## Working with the Specification + +### Understanding Specification Documents + +The specification exists in multiple forms: + +#### Formal Schema Definitions + +Zod schemas in the ATT&CK Data Model library provide executable validation rules. + +#### Reference Documentation + +Auto-generated schema documentation describes object structures and constraints. + +#### Philosophy Documents + +MITRE ATT&CK papers explain the conceptual foundations behind specification decisions. + +#### Implementation Examples + +Working code examples demonstrate specification usage patterns. + +### Common Specification Misconceptions + +#### "ATT&CK is just JSON files" + +**Reality**: ATT&CK is a structured specification with validation rules, semantic constraints, and evolution patterns. + +#### "Custom properties break STIX compliance" + +**Reality**: ATT&CK uses STIX-defined extension mechanisms to maintain full standards compliance. We currently use the STIX 2.0 compliant method of extending STIX, but plan to fully use STIX 2.1's extension definitions in the future. + +#### "ATT&CK IDs are primary identifiers" + +**Reality**: STIX IDs are primary identifiers; ATT&CK IDs are human-readable aliases stored as external references. + +#### "The specification is static" + +**Reality**: The specification evolves continuously with new object types, properties, and validation rules. + +--- diff --git a/docusaurus/docs/principles/compatibility.mdx b/docusaurus/docs/principles/compatibility.mdx new file mode 100644 index 00000000..eea1f735 --- /dev/null +++ b/docusaurus/docs/principles/compatibility.mdx @@ -0,0 +1,241 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Compatibility + + + +**Understanding version relationships and compatibility across the ATT&CK ecosystem** + +The ATT&CK Data Model operates within a complex ecosystem of interconnected version dependencies. Understanding these relationships helps you make informed decisions about version selection, upgrade timing, and compatibility management for your applications. + +## The Compatibility Challenge + +ATT&CK's ecosystem involves multiple independent components that evolve at different rates: + +- **ATT&CK Data Model Library** (`@mitre-attack/attack-data-model`) - This TypeScript library +- **ATT&CK Specification** - Defines object schemas and validation rules +- **STIX Specification** - Core STIX standard that ATT&CK extends +- **ATT&CK Dataset Releases** - The actual threat intelligence data from MITRE + +Each component has its own versioning scheme and release cycle, creating a compatibility matrix that must be carefully managed. + +## Supported Versions Compatibility Matrix + +| ADM Version | ATT&CK Specification | STIX Version | Supported ATT&CK Releases | +|-------------|---------------------|--------------|---------------------------| +| 1.x, 2.x, 3.x | 3.2.0 | 2.1 | โ‰ฅ15.x, โ‰ค17.x | +| 4.x | 3.3.0 | 2.1 | โ‰ฅ15.x, โ‰ค18.x | +| 5.x *(future)* | 4.0.0 | 2.1 | โ‰ฅ18.x | + +*Note: Other versions may work but are not officially supported or tested.* + +## Understanding Version Dependencies + +### ATT&CK Specification Evolution + +**Version 3.3.0 introduced significant changes:** + +- **New Object Types**: Detection Strategies, Analytics, and Log Sources +- **Deprecations**: Legacy Data Source detection relationships +- **Enhanced Features**: Campaign temporal tracking and asset relationship modeling + +**Version 4.0.0 (planned)** will include breaking changes: + +- Removal of deprecated data source `detects` relationships +- Potential schema changes affecting validation logic + +### Compatibility Implications + +#### Using Older ATT&CK Releases + +- May lack properties or objects expected by newer ADM versions +- Can cause validation errors or incomplete data mapping +- Generally safe for read-only analytical workflows + +#### Using Newer ATT&CK Releases + +- May introduce objects or properties not recognized by older ADM versions +- Risk of parsing failures or missing data +- Requires ADM updates for full feature support + +#### Specification Mismatches + +- Different specification versions may have incompatible validation rules +- Object schemas may differ, affecting data integrity +- Relationship types and constraints may change + +## Practical Compatibility Strategies + +### Development Environment Management + +```typescript +// Check compatibility at runtime +function validateCompatibility(attackDataModel: AttackDataModel) { + const specVersion = attackDataModel.getSpecificationVersion(); + const supportedVersions = ['3.2.0', '3.3.0']; + + if (!supportedVersions.includes(specVersion)) { + console.warn(`Specification version ${specVersion} may not be fully supported`); + } +} +``` + +### Version Pinning for Stability + +```typescript +// Pin specific versions for predictable behavior +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin to tested version + parsingMode: 'strict' +}); +``` + +### Graceful Degradation + +```typescript +// Handle version differences gracefully +function getDetectionData(technique: Technique) { + // Try modern detection strategy approach (3.3.0+) + if (technique.getDetectionStrategies) { + const strategies = technique.getDetectionStrategies(); + if (strategies.length > 0) { + return strategies; + } + } + + // Fall back to legacy data component approach + if (technique.getDetectedBy) { + return technique.getDetectedBy(); + } + + // Final fallback to detection text + return [{ description: technique.x_mitre_detection || 'No detection information available' }]; +} +``` + +## Version Migration Planning + +### Planning Specification Upgrades + +1. **Assessment Phase** + - Review changelog for breaking changes + - Identify affected code paths in your application + - Plan testing strategy for new features + +2. **Testing Phase** + - Test with representative data samples + - Validate existing functionality continues working + - Verify new features work as expected + +3. **Rollout Phase** + - Implement gradual rollout with monitoring + - Maintain rollback capability + - Update documentation and training materials + +### Handling Breaking Changes + +Breaking changes typically involve: + +- **Object Schema Changes**: New required fields or validation rules +- **Relationship Changes**: Modified relationship types or constraints +- **Deprecated Features**: Removal of legacy object types or properties + +**Migration Strategy**: + +```typescript +class VersionAwareProcessor { + processObjects(objects: AttackObject[]) { + return objects.map(obj => { + switch (this.getSpecVersion(obj)) { + case '3.2.0': + return this.processLegacyObject(obj); + case '3.3.0': + return this.processCurrentObject(obj); + default: + return this.processWithFallback(obj); + } + }); + } +} +``` + +## Compatibility Best Practices + +### For Application Developers + +1. **Version Awareness**: Always check specification versions before using new features +2. **Feature Detection**: Use capability detection over version checking when possible +3. **Flexible Parsing**: Use `relaxed` mode in production to handle data variations +4. **Comprehensive Testing**: Test with multiple ATT&CK dataset versions +5. **Documentation**: Document version requirements clearly for users + +### For Data Consumers + +1. **Stay Current**: Regularly update to supported ADM versions +2. **Monitor Changes**: Subscribe to release notes for breaking change notifications +3. **Test Early**: Test with pre-release versions when available +4. **Validate Data**: Implement data quality checks for version compatibility +5. **Plan Migrations**: Schedule regular upgrade cycles aligned with ATT&CK releases + +### For Integration Teams + +1. **Environment Isolation**: Use different versions in development vs production +2. **Automated Testing**: Include compatibility tests in CI/CD pipelines +3. **Monitoring**: Track compatibility issues in production deployments +4. **Rollback Plans**: Maintain ability to downgrade if issues arise +5. **Team Communication**: Share compatibility requirements across teams + +## Common Compatibility Issues + +### Schema Validation Failures + +**Problem**: Objects fail validation with newer specification versions + +**Solution**: + +- Update to compatible ADM version +- Use `relaxed` parsing mode temporarily +- Review and update custom validation logic + +### Missing Object Properties + +**Problem**: Expected properties don't exist in older datasets + +**Solution**: + +```typescript +// Defensive property access +const detectsBy = technique.x_mitre_data_sources || + technique.getDataComponents?.() || + []; +``` + +### Deprecated Feature Usage + +**Problem**: Application uses deprecated object types or relationships + +**Solution**: + +- Migrate to replacement features +- Implement feature detection +- Plan phased migration strategy + +## Future Compatibility Planning + +### Anticipated Changes + +- **STIX 2.2 Adoption**: May require major ADM version update +- **ATT&CK Specification 4.0**: Will remove legacy detection relationships +- **New Object Types**: Regular introduction of new ATT&CK object types +- **Performance Improvements**: Schema optimizations may affect validation + +### Staying Prepared + +1. **Follow Development**: Monitor ADM and ATT&CK development channels +2. **Participate in Community**: Engage with other users facing similar challenges +3. **Contribute Back**: Share compatibility issues and solutions with the community +4. **Plan Resources**: Budget time and resources for regular compatibility updates + +--- diff --git a/docusaurus/docs/principles/index.mdx b/docusaurus/docs/principles/index.mdx new file mode 100644 index 00000000..56373c39 --- /dev/null +++ b/docusaurus/docs/principles/index.mdx @@ -0,0 +1,35 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Guiding Principles + + + +**Understanding-oriented content about design decisions and architecture** + +This section explores the "why" behind the ATT&CK Data Model - the design decisions, architectural choices, and trade-offs that shape the library. +These explanations provide context and rationale rather than instructions, helping you understand the deeper principles that guide the project. + +## Dive Deeper + +### Foundational Context + +- **[Why the ATT&CK Data Model Exists](./why-adm-exists)** - The problem context, solution approach, and value proposition +- **[Why TypeScript Instead of X](./why-typescript)** - Why we chose TypeScript +- **[Why Zod Instead of X](./why-zod)** - Why we chose Zod over other options +- **[STIX 2.1 as the Foundation](./stix-foundation)** - Why STIX was chosen and how it shapes the architecture + +### ATT&CK Specification Understanding + +- **[ATT&CK Specification Overview](./attack-specification-overview)** - Understanding the structure and purpose of the ATT&CK specification +- **[Versioning Philosophy](./versioning-philosophy)** - Understanding ATT&CK's multi-dimensional versioning approach + +### Technical Architecture + +- **[Schema Design Principles](./schema-design)** - Validation philosophy, refinement patterns, and extensibility choices + +### Miscellaneous + +- **[Compatibility](./compatibility)** - Compatibility matrix of versions of ATT&CK, the data model, and the library +- **[Architecture & Design Trade-offs](./trade-offs)** - Reasons why architecture decisions were made + +--- diff --git a/docusaurus/docs/principles/schema-design.mdx b/docusaurus/docs/principles/schema-design.mdx new file mode 100644 index 00000000..26dfcf3d --- /dev/null +++ b/docusaurus/docs/principles/schema-design.mdx @@ -0,0 +1,480 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Schema Design Principles + + + +**Validation philosophy, refinement patterns, and extensibility choices** + +The schema layer is the foundation that enables both runtime validation and compile-time type safety in the ATT&CK Data Model. The design of these schemas reflects careful consideration of competing concerns: STIX compliance, ATT&CK domain rules, TypeScript integration, extensibility, and performance. This explanation explores the principles that guide schema design decisions. + +## Core Design Philosophy + +### Validation as Documentation + +**Principle**: Schemas serve as executable documentation of ATT&CK requirements. + +Rather than maintaining separate documentation about data format requirements, the schemas encode these requirements directly. This ensures that documentation stays synchronized with implementation and provides immediate feedback when requirements change. + +```typescript +// Schema encodes the requirement that techniques must have ATT&CK IDs +const techniqueSchema = z.object({ + external_references: z.array(externalReferenceSchema).min(1) +}).superRefine((data, ctx) => { + const firstRef = data.external_references[0]; + if (firstRef.source_name !== 'mitre-attack' || !firstRef.external_id) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'First external reference must contain ATT&CK ID' + }); + } +}); +``` + +This approach means that anyone using the schemas automatically understands ATT&CK requirements through validation feedback, rather than needing to consult separate documentation. + +### Type Inference Over Type Declaration + +**Principle**: TypeScript types are inferred from schemas, not declared separately. + +This prevents the common problem of types becoming out of sync with runtime validation: + +```typescript +// โœ… Single source of truth - types inferred from schema +const techniqueSchema = z.object({ + name: z.string(), + x_mitre_platforms: z.array(z.string()).optional() +}); +type Technique = z.infer; + +// โŒ Avoided - separate type declarations that can drift +interface Technique { + name: string; + x_mitre_platforms?: string[]; +} +const techniqueSchema = z.object(/* ... */); +``` + +**Benefits**: + +- Impossible for types to become inconsistent with validation +- Schema changes automatically propagate to TypeScript types +- Refactoring tools work across both validation and type layers + +**Trade-offs**: + +- More complex TypeScript types that reflect schema structure +- Limited ability to create "prettier" type declarations +- Schema design is constrained by TypeScript type inference capabilities + +## Schema Architecture Patterns + +### Hierarchical Schema Composition + +ATT&CK schemas are built through composition rather than inheritance, reflecting the layered nature of STIX + ATT&CK requirements: + +```typescript +// Base STIX requirements +const stixDomainObjectSchema = z.object({ + type: z.string(), + spec_version: z.literal('2.1'), + id: stixIdentifierSchema, + created: stixTimestampSchema, + modified: stixTimestampSchema +}); + +// ATT&CK-specific additions +const attackBaseObjectSchema = z.object({ + name: z.string().min(1), + description: z.string().min(1), + x_mitre_attack_spec_version: z.string(), + x_mitre_version: z.string(), + x_mitre_domains: z.array(z.enum(['enterprise-attack', 'mobile-attack', 'ics-attack'])), + external_references: z.array(externalReferenceSchema).min(1) +}); + +// Object-specific requirements +const techniqueSchema = stixDomainObjectSchema + .merge(attackBaseObjectSchema) + .extend({ + type: z.literal('attack-pattern'), + kill_chain_phases: z.array(killChainPhaseSchema), + x_mitre_platforms: z.array(z.string()).optional(), + x_mitre_is_subtechnique: z.boolean().optional() + }); +``` + +**Design Rationale**: + +- **Composition over inheritance** enables flexible schema reuse +- **Layer separation** makes it clear which requirements come from which standards +- **Merge vs extend** choice depends on whether properties can conflict + +### Refinement Pattern for Complex Validation + +Simple property validation isn't sufficient for ATT&CK's business rules. The refinement pattern handles cross-property and contextual validation: + +```typescript +const techniqueWithRefinements = techniqueSchema + .superRefine(createAttackIdRefinement()) + .superRefine(createTacticRefinement()) + .superRefine(createSubtechniqueRefinement()); + +// Refinements are factory functions for reusability +function createAttackIdRefinement() { + return (data: any, ctx: z.RefinementCtx) => { + const firstRef = data.external_references?.[0]; + if (!firstRef || firstRef.source_name !== 'mitre-attack') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'First external reference must be ATT&CK ID', + path: ['external_references', 0] + }); + } + + // Validate ATT&CK ID format + const attackId = firstRef.external_id; + if (attackId && !isValidAttackId(attackId, data.type)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Invalid ATT&CK ID format: ${attackId}`, + path: ['external_references', 0, 'external_id'] + }); + } + }; +} +``` + +**Why Factory Functions**: + +- **Reusability** across multiple schemas +- **Testability** in isolation from schemas +- **Composability** - refinements can be mixed and matched +- **Extensibility** - users can add their own refinements + +## Extensibility Architecture + +### The Refinement Challenge + +Zod's extension methods (`extend`, `merge`, `pick`, `omit`) discard refinements, creating a challenge for extensibility: + +```typescript +// โŒ This loses refinements +const extendedTechnique = techniqueSchema.extend({ + x_custom_field: z.string() +}); + +// โœ… This preserves refinements +const extendedTechnique = techniqueSchema + .extend({ x_custom_field: z.string() }) + .superRefine(createAttackIdRefinement()) // Must reapply manually + .superRefine(createTacticRefinement()); +``` + +**Design Response**: + +1. **Export refinement factories** so users can reapply them +2. **Document which refinements** each schema uses +3. **Provide extension helpers** that automate refinement reapplication + +```typescript +// Helper function for common extension pattern +export function extendAttackSchema( + baseSchema: z.ZodObject, + extensions: U, + refinements: Array<(data: any, ctx: z.RefinementCtx) => void> +) { + let extended = baseSchema.extend(extensions); + for (const refinement of refinements) { + extended = extended.superRefine(refinement); + } + return extended; +} + +// Usage +const customTechnique = extendAttackSchema( + techniqueSchema, + { x_org_threat_level: z.enum(['low', 'medium', 'high']) }, + [createAttackIdRefinement(), createTacticRefinement()] +); +``` + +### Custom Property Conventions + +STIX custom property naming conventions are enforced through schema design: + +```typescript +// Custom properties must use x_ prefix +const customPropertySchema = z.record( + z.string().regex(/^x_[a-z0-9_]+$/), // Enforce naming convention + z.unknown() // Allow any value type +); + +const extensibleObjectSchema = baseSchema.and(customPropertySchema); +``` + +**Benefits**: + +- **Standards compliance** is automatically enforced +- **Namespace conflicts** are prevented +- **Tool interoperability** is preserved + +**Limitations**: + +- **Naming restrictions** may feel constraining +- **TypeScript integration** is more complex with dynamic properties +- **Validation messages** may be unclear for naming violations + +## Validation Philosophy + +### Strict vs Relaxed Modes + +The schema design supports two validation philosophies through parsing modes: + +#### Strict Mode Philosophy + +**"Data integrity is paramount - invalid data must be rejected completely"** + +```typescript +// Strict mode - all objects must validate +const result = techniqueSchema.parse(data); // Throws on any error +``` + +**Use cases**: + +- Production applications requiring data quality guarantees +- Integration with systems that can't handle partial data +- Testing and validation scenarios + +#### Relaxed Mode Philosophy + +**"Partial data is better than no data - log errors but continue processing"** + +```typescript +// Relaxed mode - continue with valid objects +const result = techniqueSchema.safeParse(data); +if (!result.success) { + console.warn('Validation warnings:', result.error.errors); + return null; // Skip invalid objects but continue processing +} +return result.data; +``` + +**Use cases**: + +- Research and development environments +- Integration with data sources that may have minor quality issues +- Gradual data migration scenarios + +### Error Message Design + +Schema error messages are designed for developer clarity rather than end-user consumption: + +```typescript +// Good error message design +ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Technique must specify at least one platform in x_mitre_platforms', + path: ['x_mitre_platforms'] +}); + +// Poor error message design +ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid', // Too vague + path: [] // No specific location +}); +``` + +**Design principles**: + +- **Specific location** - use path arrays to pinpoint issues +- **Actionable guidance** - explain what needs to be fixed +- **Context awareness** - reference ATT&CK concepts, not just schema structure +- **Technical accuracy** - precise terminology for developers + +## Performance Considerations + +### Schema Compilation Strategy + +Schemas are designed to compile once and reuse many times: + +```typescript +// โœ… Compile schema once +const compiledSchema = techniqueSchema; + +// โœ… Reuse for multiple validations +data.forEach(item => compiledSchema.parse(item)); + +// โŒ Don't recompile schemas +data.forEach(item => z.object({/* ... */}).parse(item)); +``` + +**Optimization strategies**: + +- **Schema caching** to avoid recompilation +- **Partial compilation** for frequently-used sub-schemas +- **Lazy loading** for rarely-used validation rules + +### Memory vs Speed Tradeoffs + +Schema design balances memory usage against validation speed: + +```typescript +// Memory-efficient but slower - shared schema instances +const baseSchema = z.object({/* large schema */}); +const schema1 = baseSchema.extend({/* additions */}); +const schema2 = baseSchema.extend({/* different additions */}); + +// Faster but memory-intensive - separate compiled schemas +const schema1 = z.object({/* complete schema */}); +const schema2 = z.object({/* complete schema with duplicated parts */}); +``` + +The library chooses **memory efficiency over raw speed** because: + +- ATT&CK datasets are large but validation happens infrequently +- Schema compilation time is amortized across many validations +- Memory usage affects application scalability more than validation speed + +## Testing Strategy for Schemas + +### Positive and Negative Test Cases + +Each schema includes comprehensive test coverage: + +```typescript +describe('techniqueSchema', () => { + it('accepts valid technique objects', () => { + const validTechnique = { + type: 'attack-pattern', + id: 'attack-pattern--12345678-1234-1234-1234-123456789012', + spec_version: '2.1', + created: '2023-01-01T00:00:00.000Z', + modified: '2023-01-01T00:00:00.000Z', + name: 'Process Injection', + description: 'Adversaries may inject code...', + external_references: [{ + source_name: 'mitre-attack', + external_id: 'T1055', + url: 'https://attack.mitre.org/techniques/T1055' + }], + x_mitre_attack_spec_version: '3.3.0', + x_mitre_version: '1.0' + }; + + expect(() => techniqueSchema.parse(validTechnique)).not.toThrow(); + }); + + it('rejects techniques without ATT&CK IDs', () => { + const invalidTechnique = { + // Valid STIX structure but missing ATT&CK ID + type: 'attack-pattern', + id: 'attack-pattern--12345678-1234-1234-1234-123456789012', + // ... other required fields + external_references: [{ + source_name: 'other-source', // Not mitre-attack + external_id: 'OTHER-001' + }] + }; + + expect(() => techniqueSchema.parse(invalidTechnique)).toThrow(); + }); +}); +``` + +### Real-World Data Testing + +Schemas are tested against actual ATT&CK releases: + +```typescript +// Test against production ATT&CK data +describe('schema compatibility', () => { + it('validates current ATT&CK enterprise data', async () => { + const enterpriseData = await loadAttackData('enterprise-attack', '15.1'); + + enterpriseData.objects.forEach(obj => { + if (obj.type === 'attack-pattern') { + expect(() => techniqueSchema.parse(obj)).not.toThrow(); + } + }); + }); +}); +``` + +This ensures schemas remain compatible with real ATT&CK data evolution. + +## Future Evolution Patterns + +### Schema Versioning Strategy + +Schemas evolve alongside ATT&CK specifications: + +```typescript +// Version-aware schema selection +const getSchemaForVersion = (attackVersion: string) => { + if (attackVersion >= '3.3.0') { + return techniqueSchemaV3_3; + } else if (attackVersion >= '3.0.0') { + return techniqueSchemaV3_0; + } else { + return legacyTechniqueSchema; + } +}; +``` + +**Migration strategy**: + +- **Backward compatibility** for older ATT&CK versions +- **Gradual deprecation** of obsolete schema features +- **Clear migration paths** between schema versions + +### Extension Point Evolution + +Schema extension mechanisms evolve to support new use cases: + +```typescript +// Current extension pattern +const extended = baseSchema.extend(customFields).superRefine(customRefinement); + +// Future extension pattern (conceptual) +const extended = baseSchema.withExtensions({ + fields: customFields, + refinements: [customRefinement], + metadata: { version: '2.0', namespace: 'org' } +}); +``` + +## Living with Schema Complexity + +### When Schemas Feel Too Complex + +The ATT&CK Data Model schemas can feel overwhelming because they encode the full complexity of STIX + ATT&CK requirements. This complexity is **intentional and necessary**: + +- **STIX compliance** requires numerous fields and validation rules +- **ATT&CK domain rules** add additional constraints beyond STIX +- **Type safety** demands precise specification of all possibilities +- **Extensibility** needs mechanisms that maintain standards compliance + +### Simplification Strategies + +When working with the schemas feels too complex, consider: + +1. **Use higher-level APIs** - Implementation classes abstract schema complexity +2. **Focus on your use case** - You don't need to understand all schema features +3. **Start simple** - Begin with basic objects and add complexity gradually +4. **Leverage validation feedback** - Let schema errors guide you to correct usage + +## The Schema Design Philosophy in Practice + +The schema design reflects a philosophical commitment to: + +1. **Correctness over convenience** - Better to be precise than easy +2. **Standards compliance over optimization** - STIX compatibility is non-negotiable +3. **Type safety over flexibility** - Catch errors at compile time when possible +4. **Documentation through code** - Schemas serve as executable specifications +5. **Future-proofing over current simplicity** - Design for evolution and extension + +Understanding these philosophical commitments helps you work effectively with the schema layer and contribute to its continued development. + +--- diff --git a/docusaurus/docs/principles/stix-foundation.mdx b/docusaurus/docs/principles/stix-foundation.mdx new file mode 100644 index 00000000..dcfed435 --- /dev/null +++ b/docusaurus/docs/principles/stix-foundation.mdx @@ -0,0 +1,445 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# STIX 2.1 as the Foundation + + + +**Why STIX was chosen and how it shapes the architecture** + +The decision to build the ATT&CK Data Model on STIX 2.1 as a foundation was neither obvious nor simple. This explanation explores why STIX was chosen, what alternatives were considered, and how this foundational choice shapes every aspect of the library's architecture. + +## The Standards Decision + +### What is STIX 2.1? + +STIX (Structured Threat Information eXpression) 2.1 is an OASIS standard for representing cyber threat information in a structured, machine-readable format. It provides: + +- **Standardized object types** for representing threats, indicators, campaigns, and relationships +- **JSON-based format** that is both human-readable and machine-parsable +- **Relationship modeling** through explicit relationship objects +- **Extensibility mechanisms** for custom properties and object types +- **Version management** and object lifecycle handling + +### Why STIX for ATT&CK? + +MITRE chose STIX 2.1 as the distribution format for ATT&CK data for several strategic reasons: + +#### Industry Standardization + +**Rationale**: Using an industry standard ensures long-term viability and interoperability. + +**Benefits**: + +- ATT&CK data can be consumed by existing STIX-compliant tools +- Organizations can integrate ATT&CK into broader threat intelligence workflows +- No vendor lock-in - data remains in a standard, open format +- Skills transfer between ATT&CK and other STIX-based systems + +#### Rich Relationship Modeling + +**Rationale**: ATT&CK's value comes from relationships between objects (techniques, tactics, groups, etc.). + +**STIX provides**: + +- Explicit relationship objects with typed connections +- Bidirectional relationship navigation capabilities +- Complex relationship patterns (many-to-many, hierarchical, etc.) +- Metadata on relationships (confidence, timestamps, descriptions) + +#### Extensibility Without Breaking Standards + +**Rationale**: ATT&CK needs custom properties while remaining standards-compliant. + +**STIX enables**: + +- Custom properties with `x_` prefix namespace +- Custom object types with standard STIX structure +- Extension points that don't break parsers +- Clear separation between standard and custom elements + +## Architectural Implications + +The choice of STIX 2.1 as the foundation has profound implications for how the ATT&CK Data Model is architected: + +### Object Identity and References + +#### STIX ID Structure + +Every ATT&CK object has a STIX ID with the format `{type}--{uuid}`: + +```typescript +// STIX IDs are the primary identifiers +"id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298" +"id": "x-mitre-tactic--78b23412-0651-46d7-a540-170a1ce8bd5a" +"id": "intrusion-set--18854f55-ac7c-4634-bd9a-352dd07613b7" +``` + +**Architectural Impact**: + +- All object lookups must handle UUID-based identifiers +- Cross-references use STIX IDs, not human-readable names +- Database storage and indexing must accommodate UUID primary keys + +#### ATT&CK IDs as External References + +Human-readable ATT&CK IDs (T1055, G0006, etc.) are stored as external references, not primary identifiers: + +```json +{ + "id": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1055", + "url": "https://attack.mitre.org/techniques/T1055" + } + ] +} +``` + +**Architectural Impact**: + +- ATT&CK ID lookups require searching external references +- Validation must ensure first external reference contains ATT&CK ID +- APIs must provide both STIX ID and ATT&CK ID access patterns + +### Relationship Architecture + +#### Explicit Relationship Objects + +STIX models relationships as separate objects, not embedded references: + +```json +// โŒ Not STIX - embedded reference +{ + "type": "attack-pattern", + "tactics": ["TA0001", "TA0002"] +} + +// โœ… STIX - explicit relationship +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "attack-pattern--12345...", + "target_ref": "x-mitre-tactic--67890..." +} +``` + +**Architectural Impact**: + +- Relationship navigation requires separate relationship processing +- Memory overhead for maintaining relationship indexes +- Complex queries need to traverse multiple object types +- Relationship metadata (descriptions, confidence) naturally supported + +#### Bidirectional Relationship Handling + +STIX relationships are directional, but ATT&CK concepts often need bidirectional navigation: + +```typescript +// Forward: technique โ†’ tactics +const tactics = technique.getTactics(); + +// Reverse: tactic โ†’ techniques (derived from relationships) +const techniques = tactic.getTechniques(); +``` + +**Architectural Impact**: + +- Relationship indexes must support both forward and reverse lookups +- Implementation classes abstract bidirectional complexity +- Memory usage includes reverse relationship mappings + +### Validation Architecture + +#### Schema Compliance Layers + +STIX compliance creates multiple validation layers: + +1. **STIX Base Compliance**: All objects must be valid STIX objects +2. **ATT&CK Extensions**: ATT&CK-specific properties must be valid +3. **ATT&CK Business Rules**: Domain-specific validation (e.g., ATT&CK ID formats) + +```typescript +// Validation hierarchy +const techniqueSchema = stixDomainObjectSchema // STIX base + .extend(attackBaseObjectSchema.shape) // ATT&CK extensions + .extend(attackPatternSchema.shape) // Technique-specific + .superRefine(attackIdRefinement) // ATT&CK business rules + .superRefine(tacticRefinement); // Cross-object validation +``` + +**Architectural Impact**: + +- Complex validation chains with multiple failure points +- Error messages must map from schema violations to user-understandable problems +- Extension points must maintain STIX compliance + +#### Custom Property Naming + +STIX requires custom properties to use namespace prefixes: + +```json +{ + "type": "attack-pattern", + "name": "Process Injection", // Standard STIX + "x_mitre_platforms": ["Windows"], // ATT&CK custom + "x_mitre_is_subtechnique": false, // ATT&CK custom + "x_custom_confidence": 85 // User custom +} +``` + +**Architectural Impact**: + +- Naming conventions must be enforced in validation +- TypeScript types must accommodate x_ prefixed properties +- Schema extensions require careful namespace management + +## Benefits of STIX Foundation + +### Interoperability Advantages + +#### Ecosystem Integration + +Because ATT&CK data is valid STIX, it integrates naturally with: + +- **Threat Intelligence Platforms** (MISP, OpenCTI, ThreatConnect) +- **Security Orchestration** platforms with STIX support +- **Government systems** that mandate STIX compliance +- **Analyst tools** that consume STIX feeds + +#### Toolchain Compatibility + +STIX compliance enables: + +```typescript +// ATT&CK data can be processed by generic STIX tools +import { STIXParser } from 'stix-parser'; +import { registerDataSource } from '@mitre-attack/attack-data-model'; + +// Both approaches work with the same data +const genericParser = new STIXParser(); +const stixObjects = genericParser.parse(attackBundle); + +const attackSource = new DataSource({ source: 'file', file: 'attack.json' }); +const attackModel = loadDataModel(await registerDataSource(attackSource)); +``` + +### Long-term Sustainability + +#### Standards Evolution + +STIX provides a migration path for future evolution: + +- **STIX 2.2+**: Upgrading STIX versions brings new capabilities +- **Backward compatibility**: STIX versioning preserves older data +- **Community development**: STIX improvements benefit ATT&CK automatically + +#### Vendor Independence + +STIX ensures no single vendor controls the data format: + +- **Open standard**: OASIS governance prevents vendor lock-in +- **Multiple implementations**: Many parsers and tools available +- **Specification stability**: Changes go through formal standardization process + +## Costs of STIX Foundation + +### Complexity Overhead + +#### Learning Curve + +STIX introduces concepts that may be unfamiliar: + +- **UUID-based identifiers** instead of human-readable names +- **Relationship objects** instead of embedded references +- **External references** for human-readable identifiers +- **Custom property conventions** for extensions + +#### Implementation Complexity + +STIX compliance requires more sophisticated implementation: + +```typescript +// Simple approach - direct property access +const tacticName = technique.tactic; // โŒ Not how STIX works + +// STIX approach - relationship traversal +const tactics = technique.getTactics(); // โœ… Follows STIX patterns +const tacticNames = tactics.map(t => t.name); +``` + +### Performance Implications + +#### Memory Overhead + +STIX's explicit relationship modeling increases memory usage: + +- **Separate relationship objects** consume additional memory +- **Relationship indexes** for navigation speed +- **UUID storage** instead of smaller integer IDs + +#### Processing Complexity + +STIX relationship traversal is more complex: + +```typescript +// Direct approach - O(1) property access +const platforms = technique.platforms; + +// STIX approach - O(n) relationship lookup +const relationships = bundle.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'targets' +); +``` + +**Mitigation**: The library pre-processes relationships into indexes to restore O(1) lookup performance. + +## Alternative Approaches Considered + +### Custom JSON Format + +**Approach**: Design a custom, ATT&CK-optimized JSON structure. + +**Advantages**: + +- Simpler structure tailored to ATT&CK use cases +- Better performance through optimized layout +- No STIX complexity overhead + +**Disadvantages**: + +- No interoperability with existing tools +- Requires custom parsers for every integration +- No standards body governance +- Limited extension mechanisms + +**Why rejected**: Isolation from broader threat intelligence ecosystem outweighed performance benefits. + +### GraphQL Schema + +**Approach**: Use GraphQL to define ATT&CK data structure and relationships. + +**Advantages**: + +- Modern API technology with excellent tooling +- Built-in relationship traversal +- Type system with validation +- Query optimization capabilities + +**Disadvantages**: + +- Requires GraphQL server infrastructure +- Less suitable for static data distribution +- No existing threat intelligence ecosystem integration +- Over-engineering for data model use case + +**Why rejected**: Added infrastructure requirements without clear benefits over STIX. + +### Relational Database Schema + +**Approach**: Define ATT&CK as relational database tables with foreign keys. + +**Advantages**: + +- Mature technology with extensive tooling +- Excellent query capabilities through SQL +- Strong consistency guarantees +- Well-understood by most developers + +**Disadvantages**: + +- Requires database infrastructure for simple data access +- Poor fit for distributed, document-oriented data +- Limited extension mechanisms +- No standard for threat intelligence interchange + +**Why rejected**: Infrastructure requirements too heavy for a data modeling library. + +## Living with STIX Decisions + +### Embracing STIX Patterns + +Rather than fighting STIX complexity, the library embraces STIX patterns while abstracting complexity: + +#### Implementation Classes Hide Complexity + +```typescript +// STIX relationship traversal is complex... +const tacticRelationships = bundle.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'uses' && + bundle.objects.find(target => + target.id === obj.target_ref && + target.type === 'x-mitre-tactic' + ) +); + +// ...but implementation classes make it simple +const tactics = technique.getTactics(); +``` + +#### Validation Abstracts STIX Requirements + +```typescript +// Users don't need to understand STIX validation rules... +const isValid = techniqueSchema.safeParse(data).success; + +// ...the schema handles STIX compliance automatically +``` + +### Working with STIX Strengths + +The library leverages STIX strengths while mitigating weaknesses: + +#### Relationship Richness + +STIX's explicit relationships enable rich metadata: + +```typescript +// Relationship objects can carry additional context +const relationship = bundle.objects.find(obj => + obj.type === 'relationship' && + obj.relationship_type === 'uses' && + obj.source_ref === group.id && + obj.target_ref === technique.id +); + +// Access procedure descriptions from relationship +const procedureDescription = relationship.description; +``` + +#### Extension Mechanisms + +STIX custom properties enable organization-specific additions: + +```typescript +// Extend techniques with custom intelligence +const extendedTechniqueSchema = techniqueSchema.extend({ + x_org_threat_level: z.enum(['low', 'medium', 'high']), + x_org_last_observed: z.string().datetime() +}); +``` + +## Future Evolution + +### STIX Standards Development + +The library's STIX foundation positions it for future standards evolution: + +- **STIX 2.2**: New relationship types and object properties +- **STIX Extensions**: Formal extension mechanisms beyond custom properties +- **Performance Improvements**: Standards-level optimizations for large datasets + +### ATT&CK Specification Evolution + +STIX provides a stable foundation as ATT&CK specifications evolve: + +- **New Object Types**: STIX patterns accommodate new ATT&CK concepts +- **Relationship Types**: New relationship semantics fit STIX relationship model +- **Version Management**: STIX versioning handles ATT&CK specification updates + +--- diff --git a/docusaurus/docs/principles/trade-offs.mdx b/docusaurus/docs/principles/trade-offs.mdx new file mode 100644 index 00000000..86449a8b --- /dev/null +++ b/docusaurus/docs/principles/trade-offs.mdx @@ -0,0 +1,226 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Architecture and Design Trade-offs + + + +**Understanding the key decisions and compromises in the ATT&CK Data Model** + +Software architecture involves countless trade-offs where optimizing for one goal requires sacrificing another. +The ATT&CK Data Model makes deliberate architectural choices that prioritize certain benefits while accepting specific costs. +Understanding these trade-offs helps users make informed decisions about when and how to use the library, and explains why certain features work the way they do. + +## Performance vs. Flexibility + +### Choice: Type-Safe Schemas with Runtime Validation + +**Benefits:** + +- Compile-time error detection prevents many bugs before deployment +- IDE support with auto-completion and inline documentation +- Runtime validation ensures data quality and standards compliance +- Clear error messages help developers debug invalid data quickly + +**Costs:** + +- Schema validation adds processing overhead during data loading +- Zod schemas increase bundle size compared to simple type annotations +- Complex nested validation can be slower than basic JSON parsing +- Memory usage is higher due to maintaining both raw and validated data + +**When This Helps:** Applications requiring high data quality, strong type safety, or integration with TypeScript tooling. + +**When This Hurts:** High-performance scenarios processing massive datasets where validation overhead is significant. + +### Alternative Approaches Considered + +**JSON Schema + Type Generation:** + +- Would reduce runtime overhead but lose type-level validation +- Harder to maintain consistency between schemas and TypeScript types +- Less developer-friendly error messages + +**Plain TypeScript Types:** + +- Minimal runtime overhead but no data validation +- Risk of runtime errors from invalid data +- Difficult to ensure STIX 2.1 compliance + +## Memory Usage vs. Query Performance + +### Choice: Relationship Pre-computation and Caching + +**Benefits:** + +- Fast relationship traversal without expensive join operations +- Intuitive API methods like `technique.getTactics()` and `group.getCampaigns()` +- Consistent performance regardless of dataset size +- Enables complex relationship queries without graph database overhead + +**Costs:** + +- Higher memory usage due to storing relationship indexes +- Longer initial loading time as relationships are computed +- Memory usage grows quadratically with highly connected datasets +- Relationship updates require index rebuilding + +**When This Helps:** Interactive applications requiring fast relationship navigation, dashboards, or analysis tools. + +**When This Hurts:** Memory-constrained environments, serverless functions with size limits, or applications only needing simple object access. + +### Alternative Approaches Considered + +**On-Demand Relationship Resolution:** + +- Lower memory usage but much slower relationship queries +- Would require complex caching to maintain reasonable performance +- API inconsistency where some operations are fast and others slow + +**External Graph Database:** + +- Better performance for complex queries but significant infrastructure overhead +- Would require additional deployment complexity +- Loss of type safety in graph traversal operations + +## Standards Compliance vs. Extensibility + +### Choice: STIX 2.1 Foundation with ATT&CK Extensions + +**Benefits:** + +- Full compatibility with existing STIX 2.1 tooling and datasets +- Standards-compliant approach enables ecosystem interoperability +- ATT&CK-specific validations ensure domain model accuracy +- Clear extension points for custom fields and validation rules + +**Costs:** + +- STIX complexity adds learning curve for ATT&CK-only users +- Standards compliance limits certain API design choices +- Verbose object structures due to STIX requirements +- Extension points require careful design to maintain compliance + +**When This Helps:** Organizations with existing STIX infrastructure, tools requiring interoperability, or complex threat intelligence workflows. + +**When This Hurts:** Simple applications only needing basic ATT&CK data access, or scenarios where STIX overhead isn't justified. + +### Alternative Approaches Considered + +**ATT&CK-Native Data Model:** + +- Simpler API and smaller learning curve +- Better performance due to optimized data structures +- Loss of standards compliance and interoperability +- Custom tooling required for data exchange + +**Generic JSON Objects:** + +- Maximum flexibility and minimal constraints +- Loss of type safety, validation, and relationship navigation +- Increased development burden on applications + +## Synchronous vs. Asynchronous API Design + +### Choice: Synchronous Primary API with Async Registration + +**Benefits:** + +- Simple, intuitive API for data access operations +- No async complexity for common use cases like `model.techniques` +- Predictable performance characteristics for queries +- Easy integration with existing synchronous code + +**Costs:** + +- Initial data registration must be async (network/file operations) +- Cannot easily support streaming or progressive data loading +- Memory must hold entire dataset during synchronous operations +- Less suitable for reactive or event-driven architectures + +**When This Helps:** Traditional application architectures, data analysis scripts, or scenarios requiring predictable synchronous data access. + +**When This Hurts:** Event-driven systems, applications requiring progressive data loading, or memory-constrained streaming scenarios. + +## Multi-Domain vs. Single-Domain Optimization + +### Choice: Unified API Supporting Enterprise, Mobile, and ICS + +**Benefits:** + +- Single library handles all ATT&CK domains uniformly +- Consistent API across different domain types +- Easy cross-domain analysis and comparison +- Simplified dependency management for multi-domain applications + +**Costs:** + +- Larger bundle size even for single-domain usage +- Schema complexity must handle domain-specific variations +- Memory overhead for unused domain capabilities +- Potential performance impact from domain abstraction layer + +**When This Helps:** Comprehensive threat analysis, research across multiple domains, or organizations using multiple ATT&CK domains. + +**When This Hurts:** Specialized applications focused on single domain, mobile applications with size constraints, or embedded systems. + +## Error Handling Philosophy + +### Choice: Strict Validation with Relaxed Mode Option + +**Benefits:** + +- Default strict mode ensures data quality and early error detection +- Relaxed mode provides fallback for handling imperfect real-world data +- Clear error messages help developers identify and fix data issues +- Configurable behavior allows different use case optimization + +**Costs:** + +- Strict mode may reject usable data due to minor validation issues +- Error handling complexity increases application development burden +- Relaxed mode can lead to subtle bugs from incomplete data processing +- Debugging can be difficult when validation rules are complex + +**When This Helps:** Production applications requiring data quality assurance, development environments where early error detection is valuable. + +**When This Hurts:** Research scenarios with experimental data, rapid prototyping, or integration with legacy systems producing imperfect data. + +## Development and Maintenance Implications + +### Backward Compatibility vs. Evolution + +We decided to make use of Semantic Versioning with Major Version Breaking Changes + +**Benefits:** Clear compatibility promises and smooth upgrade paths for minor/patch versions. + +**Costs:** Major version upgrades can require significant application changes, slowing adoption of new features. + +## Guidance for Different Use Cases + +### High-Performance Scenarios + +- Consider simpler data access patterns to minimize validation overhead +- Use relaxed parsing mode if data quality requirements permit +- Profile memory usage and consider single-domain usage if appropriate + +### Memory-Constrained Environments + +- Evaluate whether relationship pre-computation benefits justify memory costs +- Consider processing data in smaller batches if possible +- Monitor memory usage patterns and optimize based on actual usage + +### Standards-Compliant Integrations + +- Leverage STIX 2.1 compliance for interoperability benefits +- Use extension points carefully to maintain standards compliance +- Design custom validations to complement rather than replace STIX requirements + +### Rapid Development and Prototyping + +- Use relaxed parsing mode to handle imperfect data gracefully +- Focus on synchronous API patterns for simpler control flow +- Consider single-domain optimization if working with specific domains + +Understanding these trade-offs helps you make informed decisions about whether the ATT&CK Data Model's approach aligns with your specific requirements and constraints. + +--- diff --git a/docusaurus/docs/principles/versioning-philosophy.mdx b/docusaurus/docs/principles/versioning-philosophy.mdx new file mode 100644 index 00000000..cbc1a488 --- /dev/null +++ b/docusaurus/docs/principles/versioning-philosophy.mdx @@ -0,0 +1,509 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Versioning Philosophy + + + +**Understanding ATT&CK's multi-dimensional versioning approach** + +ATT&CK employs a sophisticated three-dimensional versioning system that tracks changes across different scopes and timeframes. This approach often confuses users who expect simple sequential versioning, but the complexity serves important purposes: enabling independent evolution of standards, specifications, and content while maintaining compatibility across the ecosystem. Understanding this versioning philosophy is crucial for building robust applications and managing long-term compatibility. + +## The Versioning Challenge + +### Multiple Evolution Axes + +ATT&CK data evolves along several independent dimensions, each with different change frequencies, compatibility implications, and governance models: + +#### Standards Evolution (STIX 2.1) + +- **Scope**: Core STIX specification changes +- **Frequency**: Infrequent (years between major versions) +- **Governance**: OASIS standards organization +- **Impact**: Affects all STIX-compliant tools and data + +#### Specification Evolution (ATT&CK Extensions) + +- **Scope**: ATT&CK-specific schema and validation rules +- **Frequency**: Moderate (months to years) +- **Governance**: MITRE ATT&CK team +- **Impact**: Affects ATT&CK data structure and processing logic + +#### Content Evolution (Individual Objects) + +- **Scope**: Individual technique descriptions, relationships, metadata +- **Frequency**: Frequent (weekly to monthly) +- **Governance**: ATT&CK content curators +- **Impact**: Affects analytical accuracy and completeness + +### The Impossibility of Single-Dimension Versioning + +A single version number cannot adequately express changes across these different dimensions: + +**Sequential numbering** (1.0, 2.0, 3.0): + +- โœ… Simple to understand +- โŒ Cannot distinguish between breaking specification changes and minor content updates +- โŒ Forces synchronized releases across independent change streams + +**Date-based versioning** (2023.01, 2023.02): + +- โœ… Clear temporal ordering +- โŒ No semantic information about change impact +- โŒ Doesn't indicate compatibility implications + +**Semantic versioning** (MAJOR.MINOR.PATCH): + +- โœ… Expresses compatibility impact +- โŒ Assumes single change stream +- โŒ Difficult to coordinate across independent governance models + +**Solution**: Use multiple version dimensions, each optimized for tracking changes in its specific domain. + +## The Three Version Dimensions + +### 1. STIX Version (`spec_version`) + +**Format**: `"2.1"` +**Scope**: STIX specification compliance +**Managed by**: OASIS standards organization + +#### Purpose and Characteristics + +**Standards tracking**: Indicates which version of the STIX specification the object conforms to. + +**Parser compatibility**: Enables STIX parsers to determine processing capabilities and requirements. + +**Ecosystem coordination**: Provides a standard reference point for tool compatibility across the threat intelligence ecosystem. + +**Example usage**: + +```json +{ + "spec_version": "2.1", + "type": "attack-pattern", + "id": "attack-pattern--12345..." +} +``` + +#### Evolution Patterns + +**Rare changes**: STIX versions change infrequently (every few years) through formal standardization processes. + +**Breaking changes**: Major STIX version updates may introduce incompatible changes requiring parser updates. + +**Backward compatibility**: STIX generally maintains compatibility within major versions (2.0 โ†’ 2.1 was largely additive). + +#### When STIX Version Changes + +**Parser updates required**: Applications must update STIX processing logic for new specification versions. + +**Validation changes**: Schema validation rules may require updates for new STIX features. + +**Ecosystem coordination**: Tool updates typically occur gradually across the threat intelligence ecosystem. + +### 2. ATT&CK Specification Version (`x_mitre_attack_spec_version`) + +**Format**: `"3.3.0"` (semantic versioning) +**Scope**: ATT&CK specification extensions and structure +**Managed by**: MITRE ATT&CK team + +#### Purpose and Characteristics + +**Specification compatibility**: Tracks changes to ATT&CK's extensions of STIX (custom object types, properties, validation rules). + +**Breaking change communication**: Major version increments indicate incompatible changes that may require application updates. + +**Feature availability**: Indicates which ATT&CK features and object types are available in the data. + +**Example usage**: + +```json +{ + "x_mitre_attack_spec_version": "3.3.0", + "type": "x-mitre-detection-strategy", + "x_mitre_analytics": ["analytic-id-1", "analytic-id-2"] +} +``` + +#### Version Semantics + +**Major versions (3.0.0 โ†’ 4.0.0)**: + +- Introduce breaking changes to object schemas or validation rules +- May remove deprecated features or object types +- Require application updates for compatibility + +**Minor versions (3.2.0 โ†’ 3.3.0)**: + +- Add new object types or properties in backward-compatible ways +- Introduce new features that don't break existing processing +- Applications continue working but may miss new capabilities + +**Patch versions (3.3.0 โ†’ 3.3.1)**: + +- Fix bugs in validation rules or schema definitions +- Clarify ambiguous specification language +- Should not affect application compatibility + +#### Recent Specification Evolution + +**Version 3.3.0 changes**: + +- Introduced Detection Strategy framework (analytics, log sources) +- Deprecated legacy data source detection relationships +- Added campaign temporal tracking properties +- Enhanced asset relationship modeling + +**Version 4.0.0 (planned)**: + +- Will remove deprecated data source `detects` relationships +- May introduce additional breaking changes to object schemas + +### 3. Object Version (`x_mitre_version`) + +**Format**: `"1.2"` (major.minor) +**Scope**: Individual object content and metadata +**Managed by**: ATT&CK content curators + +#### Purpose and Characteristics + +**Content tracking**: Indicates when object content (description, metadata, relationships) has been meaningfully updated. + +**Change detection**: Enables applications to identify when objects have been modified since last processing. + +**Analytical currency**: Helps analysts understand the recency and evolution of threat intelligence. + +**Example usage**: + +```json +{ + "x_mitre_version": "1.3", + "name": "Process Injection", + "description": "Updated with new detection guidance...", + "modified": "2023-06-15T10:30:00.000Z" +} +``` + +#### Version Semantics + +**Major increments (1.0 โ†’ 2.0)**: + +- Substantial content changes that affect analytical interpretation +- New relationships, significantly updated descriptions, or revised metadata +- May indicate changes in understanding of adversary behavior + +**Minor increments (1.2 โ†’ 1.3)**: + +- Incremental content improvements, clarifications, or additions +- Editorial improvements, additional examples, or minor metadata updates +- Generally preserve existing analytical conclusions + +#### Object Version Lifecycle + +```shell +Creation โ†’ 1.0 โ†’ 1.1 โ†’ 1.2 โ†’ 2.0 โ†’ 2.1 โ†’ 3.0... + โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€ Major content revision + โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€ Minor improvement + โ”‚ โ”‚ โ”‚ โ””โ”€ Major analytical update + โ”‚ โ”‚ โ””โ”€ Minor content addition + โ”‚ โ””โ”€ First content improvement + โ””โ”€ Initial creation +``` + +## Version Relationship Patterns + +### Independent Evolution + +The three version dimensions evolve independently, creating complex interaction patterns: + +```json +// Object may have different version update frequencies +{ + "spec_version": "2.1", // Unchanged for years + "x_mitre_attack_spec_version": "3.3.0", // Updated every few months + "x_mitre_version": "2.4", // Updated frequently + "modified": "2023-08-15T14:20:00.000Z" +} +``` + +### Compatibility Matrix + +Different version combinations create compatibility requirements: + +| STIX Version | ATT&CK Spec Version | Compatibility Notes | +|--------------|---------------------|---------------------| +| 2.1 | 3.0.0-3.2.x | Legacy data source model | +| 2.1 | 3.3.0+ | Detection strategy model introduced | +| 2.1 | 4.0.0+ | Legacy data sources removed | + +### Version-Aware Processing + +Applications should adapt behavior based on specification versions: + +```typescript +function processDetectionData(technique: Technique) { + const specVersion = technique.x_mitre_attack_spec_version; + + if (semver.gte(specVersion, '3.3.0')) { + // Use new detection strategy relationships + return technique.getDetectionStrategies(); + } else { + // Fall back to legacy data component relationships + return technique.getDetectedBy(); // Deprecated but still functional + } +} +``` + +## Versioning Trade-offs and Benefits + +### Benefits of Multi-Dimensional Versioning + +#### Independent Evolution + +Different aspects of ATT&CK can evolve at their natural pace without forcing artificial synchronization: + +- **Content updates** can happen frequently as new research emerges +- **Specification changes** can follow careful design and review cycles +- **Standards evolution** can proceed through formal governance processes + +#### Precise Compatibility Signaling + +Applications can make informed decisions about feature support and compatibility: + +```typescript +// Precise compatibility checking +if (semver.gte(object.x_mitre_attack_spec_version, '3.3.0')) { + // Safe to use detection strategy features +} else { + // Must use legacy detection patterns +} +``` + +#### Ecosystem Stability + +Different components of the ATT&CK ecosystem can update independently without breaking other components. + +### Costs of Multi-Dimensional Versioning + +#### Complexity Burden + +Developers must understand and track multiple version dimensions, each with different semantics and update patterns. + +#### Compatibility Matrix Management + +The combination of version dimensions creates a complex compatibility matrix that's difficult to test comprehensively. + +#### Migration Path Complexity + +Upgrading applications may require consideration of multiple version dimensions and their interactions. + +## Version Management Strategies + +### For Application Developers + +#### Version Compatibility Checking + +```typescript +interface VersionRequirements { + minStixVersion?: string; + minAttackSpecVersion?: string; + maxAttackSpecVersion?: string; +} + +function validateCompatibility( + object: AttackObject, + requirements: VersionRequirements +): boolean { + if (requirements.minAttackSpecVersion && + semver.lt(object.x_mitre_attack_spec_version, requirements.minAttackSpecVersion)) { + return false; + } + + if (requirements.maxAttackSpecVersion && + semver.gt(object.x_mitre_attack_spec_version, requirements.maxAttackSpecVersion)) { + return false; + } + + return true; +} +``` + +#### Feature Detection over Version Checking + +```typescript +// โœ… Better: Feature detection +function supportsDetectionStrategies(technique: Technique): boolean { + return typeof technique.getDetectionStrategies === 'function'; +} + +// โŒ Fragile: Version checking +function supportsDetectionStrategies(technique: Technique): boolean { + return semver.gte(technique.x_mitre_attack_spec_version, '3.3.0'); +} +``` + +### For Data Consumers + +#### Change Detection Workflows + +```typescript +// Track object versions to detect updates +interface ObjectVersionCache { + stixId: string; + lastSeenVersion: string; + lastProcessed: Date; +} + +function detectUpdatedObjects( + cache: ObjectVersionCache[], + newData: AttackObject[] +): AttackObject[] { + return newData.filter(obj => { + const cached = cache.find(c => c.stixId === obj.id); + return !cached || cached.lastSeenVersion !== obj.x_mitre_version; + }); +} +``` + +### For Tool Integrators + +#### Graceful Degradation Patterns + +```typescript +// Support multiple specification versions gracefully +class AttackProcessor { + processDetection(technique: Technique) { + // Try modern detection strategy approach + const strategies = this.tryGetDetectionStrategies(technique); + if (strategies.length > 0) { + return this.processDetectionStrategies(strategies); + } + + // Fall back to legacy data component approach + const dataComponents = this.tryGetDataComponents(technique); + if (dataComponents.length > 0) { + return this.processDataComponents(dataComponents); + } + + // Fall back to basic detection text + return this.processDetectionText(technique.x_mitre_detection); + } +} +``` + +## Version Evolution Patterns + +### Predictable Change Cycles + +#### Content Updates (Object Versions) + +- **Frequency**: Weekly to monthly +- **Scope**: Individual object improvements +- **Impact**: Usually additive, may refine analytical conclusions + +#### Specification Updates (ATT&CK Spec Version) + +- **Frequency**: Every 3-6 months +- **Scope**: New object types, properties, or validation rules +- **Impact**: May require application updates for full feature support + +#### Standards Updates (STIX Version) + +- **Frequency**: Every 1-3 years +- **Scope**: Core STIX specification changes +- **Impact**: May require parser and validation logic updates + +### Migration Planning + +#### Specification Version Upgrades + +1. **Review changes**: Understand new features and breaking changes +2. **Test compatibility**: Verify existing code works with new specification +3. **Implement new features**: Add support for new object types or properties +4. **Update validation**: Modify schema validation for new requirements +5. **Plan rollback**: Maintain ability to process older specification versions + +#### Handling Breaking Changes + +```typescript +// Support multiple specification versions during transitions +class VersionAwareProcessor { + processObject(obj: AttackObject) { + switch (obj.x_mitre_attack_spec_version) { + case '3.2.x': + return this.processLegacyFormat(obj); + case '3.3.x': + return this.processCurrentFormat(obj); + case '4.0.x': + return this.processFutureFormat(obj); + default: + throw new UnsupportedVersionError(obj.x_mitre_attack_spec_version); + } + } +} +``` + +## Common Versioning Mistakes + +### 1. Ignoring Specification Versions + +**โŒ Problematic**: + +```typescript +// Assuming all objects support modern features +const strategies = technique.getDetectionStrategies(); +``` + +**โœ… Correct**: + +```typescript +// Check specification version before using new features +if (semver.gte(technique.x_mitre_attack_spec_version, '3.3.0')) { + const strategies = technique.getDetectionStrategies(); +} +``` + +### 2. Confusing Version Semantics + +**โŒ Problematic**: + +```typescript +// Using object version to determine feature availability +if (technique.x_mitre_version >= '2.0') { + // Object version doesn't indicate feature availability +} +``` + +**โœ… Correct**: + +```typescript +// Use specification version for feature detection +if (semver.gte(technique.x_mitre_attack_spec_version, '3.3.0')) { + // Specification version indicates feature availability +} +``` + +### 3. Hard-coding Version Requirements + +**โŒ Problematic**: + +```typescript +// Brittle version checking +const REQUIRED_SPEC_VERSION = '3.3.0'; +if (obj.x_mitre_attack_spec_version !== REQUIRED_SPEC_VERSION) { + throw new Error('Unsupported version'); +} +``` + +**โœ… Better**: + +```typescript +// Flexible version range checking +const MIN_SPEC_VERSION = '3.3.0'; +if (semver.lt(obj.x_mitre_attack_spec_version, MIN_SPEC_VERSION)) { + throw new Error(`Requires spec version ${MIN_SPEC_VERSION} or higher`); +} +``` + +--- diff --git a/docusaurus/docs/principles/why-adm-exists.mdx b/docusaurus/docs/principles/why-adm-exists.mdx new file mode 100644 index 00000000..ec55b802 --- /dev/null +++ b/docusaurus/docs/principles/why-adm-exists.mdx @@ -0,0 +1,271 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Why the ATT&CK Data Model Exists + + + +The ATT&CK Data Model library didn't emerge from a vacuum - it was created to solve specific, recurring problems that users face when working with MITRE ATT&CK data. +Understanding these problems and the solution approach helps clarify when and why to use this library. + +## The Problem + +### ATT&CK Data Complexity + +MITRE ATT&CK, while invaluable for threat intelligence and security operations, presents several challenges when used programmatically: + +**Raw Data Challenges**: + +- ATT&CK data is distributed as STIX 2.1 JSON bundles containing thousands of objects +- Relationships between objects are primarily expressed through separate relationship objects, not direct references +- Objects have complex validation rules that aren't immediately obvious from the JSON structure +- Different ATT&CK domains (Enterprise, Mobile, ICS) have subtle but important differences + +**Developer Experience Challenges**: + +- No built-in type safety - easy to access non-existent properties or use wrong data types +- Relationship navigation requires manual lookup and cross-referencing +- No clear patterns for common operations like "find all techniques used by a group" + +### Before the ATT&CK Data Model + +Prior to this library, developers typically handled ATT&CK data in one of two ways: + +#### 1. Direct JSON Manipulation + +```javascript +// Raw approach - fragile and error-prone +const attackData = JSON.parse(fs.readFileSync('enterprise-attack.json')); +const techniques = attackData.objects.filter(obj => obj.type === 'attack-pattern'); + +// Find tactics for a technique - manual relationship lookup +const technique = techniques.find(t => t.external_references[0].external_id === 'T1055'); +const tacticRelationships = attackData.objects.filter(obj => + obj.type === 'relationship' && + obj.source_ref === technique.id && + obj.relationship_type === 'uses' +); +const tactics = tacticRelationships.map(rel => + attackData.objects.find(obj => obj.id === rel.target_ref) +); +``` + +**Problems**: + +- No type safety +- Manual relationship traversal +- No validation +- Fragile to data structure changes + +#### 2. Custom Wrapper Classes + +```javascript +// Custom approach - reinventing the wheel +class AttackDataParser { + constructor(jsonData) { + this.techniques = jsonData.objects.filter(obj => obj.type === 'attack-pattern'); + this.tactics = jsonData.objects.filter(obj => obj.type === 'x-mitre-tactic'); + // ... more filtering and indexing + } + + getTacticsForTechnique(techniqueId) { + // Custom relationship logic + // ... dozens of lines of relationship traversal + } +} +``` + +**Problems**: + +- Reinventing relationship logic +- Inconsistent validation approaches +- No standard patterns across teams +- Maintenance burden for each team + +### The Core Problems + +Through observing these patterns across users, several core problems emerged: + +1. **Repetitive Boilerplate**: Every team and tool reimplemented the same relationship navigation logic +2. **Validation Gaps**: Data quality issues only discovered at runtime +3. **Knowledge Duplication**: ATT&CK domain knowledge scattered across individual implementations +4. **Version Compatibility**: Difficulty updating to new ATT&CK versions +5. **Testing Challenges**: Hard to test applications against different ATT&CK datasets + +## The Solution + +### Design Philosophy + +The ATT&CK Data Model was designed with a specific philosophy: + +**"Provide a type-safe, relationship-aware, standards-compliant interface to ATT&CK data that eliminates boilerplate while preserving flexibility."** + +This philosophy drives several key decisions: + +#### Standards Compliance First + +Rather than creating a new data format, the library builds on STIX 2.1 compliance. This ensures: + +- Interoperability with existing STIX tools +- Long-term compatibility as standards evolve +- No vendor lock-in - data remains in standard formats + +#### Type Safety Without Performance Cost + +TypeScript integration provides compile-time safety while Zod schemas enable runtime validation. This combination eliminates entire classes of bugs while maintaining performance. + +#### Relationship Navigation as First-Class Feature + +Instead of treating relationships as secondary, the library makes relationship traversal the primary interface. Methods like `technique.getTactics()` abstract away the complexity of STIX relationship objects. + +### Architecture Decisions + +#### Three-Layer Design + +1. **Schema Layer**: Zod schemas provide validation and TypeScript type inference +2. **Class Layer**: Implementation classes add relationship navigation methods +3. **Data Layer**: Flexible data source architecture supports multiple ATT&CK distributions + +This separation allows using only the layers you need - schemas for validation, classes for navigation, or the complete stack for full functionality. + +#### Immutable Data Model + +ATT&CK data represents shared threat intelligence that shouldn't be accidentally modified. Immutability prevents bugs while enabling safe concurrent access. + +#### Extensibility Through Standards + +Custom fields and extensions follow STIX conventions, ensuring that customizations remain interoperable with other STIX tools. + +## Value Proposition + +### For Security Engineers + +**Before**: "I spend hours writing relationship traversal code and debugging data format issues." + +**After**: "I focus on security logic while the library handles data complexity." + +```typescript +// Before - manual relationship lookup +const tacticsForTechnique = attackData.objects + .filter(obj => obj.type === 'relationship' && obj.source_ref === techniqueId) + .map(rel => attackData.objects.find(obj => obj.id === rel.target_ref)); + +// After - simple method call +const tactics = technique.getTactics(); +``` + +### For Application Developers + +**Before**: "I'm not sure if my code will break when ATT&CK releases new data." + +**After**: "Type safety and validation catch issues at build time, not runtime." + +```typescript +// TypeScript catches these errors at compile time +const technique = attackDataModel.techniques[0]; +technique.nonExistentProperty; // โŒ Compile error +technique.name.toNumber(); // โŒ Compile error - name is string +technique.name.toUpperCase(); // โœ… Works - IntelliSense shows available methods +``` + +### For Research Teams + +**Before**: "Each researcher implements their own ATT&CK data parsing, leading to inconsistent results." + +**After**: "Standardized data access ensures reproducible research across team members." + +```typescript +// Consistent API across different research projects +const credentialAccessTechniques = attackDataModel.techniques.filter(technique => + technique.getTactics().some(tactic => tactic.name === 'Credential Access') +); +``` + +### For Enterprise Security Teams + +**Before**: "We can't confidently deploy ATT&CK-based tools because data validation is unreliable." + +**After**: "Strict validation and type safety enable confident production deployments." + +```typescript +// Validation ensures data meets all ATT&CK requirements +try { + const uuid = await registerDataSource(dataSource); + const model = loadDataModel(uuid); // Guaranteed valid data +} catch (ValidationError) { + // Handle invalid data before it reaches production +} +``` + +## When to Use This Library + +### Strong Fit Scenarios + +The ATT&CK Data Model is particularly valuable when you: + +- **Build applications** that work with ATT&CK data regularly +- **Need reliability** in production systems processing threat intelligence +- **Want type safety** to catch errors at development time +- **Work across teams** and need consistent ATT&CK data access patterns +- **Update frequently** to new ATT&CK versions and need migration confidence +- **Integrate with STIX tools** while maintaining ATT&CK-specific functionality + +### Alternative Approaches + +The library might not be the best fit if you: + +- **Need minimal dependencies** and are comfortable with manual JSON processing +- **Use non-STIX ATT&CK distributions** exclusively +- **Only need one-time data analysis** rather than ongoing application development +- **Work primarily with non-ATT&CK threat intelligence** frameworks + +## Impact on the Ecosystem + +### Standardization Benefits + +By providing a common interface to ATT&CK data, the library enables: + +- **Consistent tooling** across different organizations +- **Shareable code patterns** for common ATT&CK operations +- **Reduced learning curve** for new team members +- **Improved integration** between different security tools + +### Community Development + +The library serves as a foundation for: + +- **Specialized ATT&CK tools** that build on the data model +- **Educational resources** that can assume a common data interface +- **Research reproducibility** through standardized data access +- **Best practices** for working with structured threat intelligence + +## Future Evolution + +### Adaptation to Standards + +As STIX and ATT&CK specifications evolve, the library serves as an adaptation layer: + +- **Version compatibility** handling across ATT&CK releases +- **Standards updates** integrated transparently for users +- **Deprecation management** with clear migration paths +- **Extension points** for emerging threat intelligence standards + +### Ecosystem Growth + +The library enables ecosystem development through: + +- **Plugin architectures** for custom data sources +- **Extension patterns** for organization-specific additions +- **Integration points** with security orchestration platforms +- **API stability** that enables long-term tool development + +## Measuring Success + +The ATT&CK Data Model's success is measured by: + +- **Reduced development time** for ATT&CK-based applications +- **Fewer runtime errors** related to data handling +- **Increased adoption** of ATT&CK in security tools +- **Community contributions** that extend the library's capabilities +- **Industry standardization** around common ATT&CK data patterns + +--- diff --git a/docusaurus/docs/principles/why-typescript.mdx b/docusaurus/docs/principles/why-typescript.mdx new file mode 100644 index 00000000..e69de29b diff --git a/docusaurus/docs/principles/why-zod.mdx b/docusaurus/docs/principles/why-zod.mdx new file mode 100644 index 00000000..1b7f5988 --- /dev/null +++ b/docusaurus/docs/principles/why-zod.mdx @@ -0,0 +1,26 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Why Zod Instead of X + + + +## JSON Schema + +- **Context**: Need for runtime validation and TypeScript integration +- **Decision**: Use Zod for schema definition +- **Alternatives Considered**: JSON Schema, Pydantic (when we were considering using Python) + +**Rationale**: + +- Zod provides TypeScript type inference automatically +- Runtime validation matches compile-time types exactly +- Schema definitions are more maintainable in TypeScript +- Better error messages for developers + +**Trade-offs**: + +- Zod is less universally known than JSON Schema +- Schemas are tied to TypeScript ecosystem +- Larger runtime bundle due to Zod dependency + +--- diff --git a/docusaurus/docs/reference/api/attack-data-model.mdx b/docusaurus/docs/reference/api/attack-data-model.mdx new file mode 100644 index 00000000..025e0903 --- /dev/null +++ b/docusaurus/docs/reference/api/attack-data-model.mdx @@ -0,0 +1,304 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# AttackDataModel + + + +**Main class containing all ATT&CK objects with automatic relationship mapping** + +The `AttackDataModel` class is the central data structure that holds all ATT&CK objects and provides access to them through typed properties. It automatically processes relationships between objects and provides convenient access methods. + +## Constructor + +```typescript +new AttackDataModel(uuid: string, attackObjects: AttackObject[]) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `uuid` | `string` | Unique identifier for this data model instance | +| `attackObjects` | `AttackObject[]` | Array of validated ATT&CK objects to populate the model | + +### Example + +```typescript +import { AttackDataModel } from '@mitre-attack/attack-data-model'; + +// Usually created internally by loadDataModel() +const attackDataModel = new AttackDataModel(uuid, validatedObjects); +``` + +## Properties + +### Object Collections + +All ATT&CK objects are organized into typed arrays accessible as properties: + +| Property | Type | Description | +|----------|------|-------------| +| `techniques` | `TechniqueImpl[]` | All techniques and sub-techniques | +| `tactics` | `TacticImpl[]` | All tactics (adversary goals) | +| `groups` | `GroupImpl[]` | All threat actor groups | +| `campaigns` | `CampaignImpl[]` | All attack campaigns | +| `malware` | `MalwareImpl[]` | All malware software | +| `tools` | `ToolImpl[]` | All legitimate tools used maliciously | +| `mitigations` | `MitigationImpl[]` | All defensive measures | +| `relationships` | `RelationshipImpl[]` | All relationships between objects | +| `matrices` | `MatrixImpl[]` | All ATT&CK matrices | +| `dataSources` | `DataSourceImpl[]` | All data sources *(deprecated)* | +| `dataComponents` | `DataComponentImpl[]` | All data components *(deprecated)* | +| `assets` | `AssetImpl[]` | All targeted assets | +| `detectionStrategies` | `DetectionStrategyImpl[]` | All detection strategies | +| `analytics` | `AnalyticImpl[]` | All analytics | +| `logSources` | `LogSourceImpl[]` | All log sources | + +### Example Access + +```typescript +const attackDataModel = loadDataModel(uuid); + +// Access techniques +console.log(`Loaded ${attackDataModel.techniques.length} techniques`); + +// Access tactics +const initialAccess = attackDataModel.tactics.find(t => + t.x_mitre_shortname === 'initial-access' +); + +// Access groups +const apt1 = attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' +); +``` + +## Methods + +### getUuid() + +Returns the unique identifier for this data model instance. + +```typescript +getUuid(): string +``` + +**Returns**: The UUID assigned during data source registration. + +**Example**: + +```typescript +const modelId = attackDataModel.getUuid(); +console.log(`Model ID: ${modelId}`); +``` + +### getObjectById() + +Retrieves any ATT&CK object by its STIX ID. + +```typescript +getObjectById(id: string): AttackObject | undefined +``` + +**Parameters**: + +- `id` - STIX identifier (e.g., `"attack-pattern--12345678-..."`) + +**Returns**: The object with matching STIX ID, or `undefined` if not found. + +**Example**: + +```typescript +const technique = attackDataModel.getObjectById( + 'attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298' +); + +if (technique) { + console.log(`Found: ${technique.name}`); +} +``` + +### getObjectByAttackId() + +Retrieves any ATT&CK object by its ATT&CK ID. + +```typescript +getObjectByAttackId(attackId: string): AttackObject | undefined +``` + +**Parameters**: + +- `attackId` - ATT&CK identifier (e.g., `"T1055"`, `"G0006"`, `"S0001"`) + +**Returns**: The object with matching ATT&CK ID, or `undefined` if not found. + +**Example**: + +```typescript +const technique = attackDataModel.getObjectByAttackId('T1055'); +const group = attackDataModel.getObjectByAttackId('G0006'); +const software = attackDataModel.getObjectByAttackId('S0001'); + +if (technique) { + console.log(`T1055: ${technique.name}`); +} +``` + +### getAllObjects() + +Returns all objects in the data model as a flat array. + +```typescript +getAllObjects(): AttackObject[] +``` + +**Returns**: Array containing all objects from all collections. + +**Example**: + +```typescript +const allObjects = attackDataModel.getAllObjects(); +console.log(`Total objects: ${allObjects.length}`); + +// Count by type +const counts = allObjects.reduce((acc, obj) => { + acc[obj.type] = (acc[obj.type] || 0) + 1; + return acc; +}, {} as Record); + +console.log('Object counts:', counts); +``` + +## Relationship Processing + +The `AttackDataModel` automatically processes all relationship objects during construction to enable convenient navigation between related objects. + +### Automatic Relationship Mapping + +When the data model is created, it: + +1. **Indexes all relationships** by source and target IDs +2. **Creates bidirectional mappings** where appropriate +3. **Populates navigation methods** on implementation classes +4. **Validates relationship integrity** in strict mode + +### Relationship Types Processed + +| Relationship | Description | Navigation Methods | +|--------------|-------------|-------------------| +| `uses` | Groups/Software โ†’ Techniques | `getTechniques()`, `getSoftware()` | +| `mitigates` | Mitigations โ†’ Techniques | `getMitigations()`, `getTechniques()` | +| `subtechnique-of` | Sub-techniques โ†’ Parent | `getSubtechniques()`, `getParentTechnique()` | +| `attributed-to` | Campaigns โ†’ Groups | `getAttributedTo()`, `getAssociatedCampaigns()` | +| `targets` | Techniques โ†’ Assets | `getTargets()`, `getTargetingTechniques()` | + +### Example: Relationship Navigation + +```typescript +// Start with a technique +const technique = attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' +); + +if (technique) { + // Navigate to related objects + const tactics = technique.getTactics(); + const mitigations = technique.getMitigations(); + const subtechniques = technique.getSubtechniques(); + + console.log(`${technique.name} is used for ${tactics.length} tactics`); + console.log(`${mitigations.length} mitigations available`); + console.log(`${subtechniques.length} sub-techniques defined`); +} +``` + +## Data Integrity + +### Validation + +The `AttackDataModel` ensures data integrity through: + +- **Schema validation** of all input objects +- **Relationship validation** between objects +- **ID uniqueness** checking +- **Reference integrity** validation + +### Error Handling + +In strict parsing mode, the following issues cause errors: + +- Missing required properties +- Invalid STIX ID formats +- Broken relationship references +- Duplicate object IDs + +In relaxed mode, these issues generate warnings but don't prevent data loading. + +## Performance Considerations + +### Memory Usage + +The `AttackDataModel` maintains all objects in memory for fast access. Typical memory usage: + +- **Enterprise ATT&CK**: ~50-100 MB +- **Mobile ATT&CK**: ~20-30 MB +- **ICS ATT&CK**: ~10-20 MB + +### Access Patterns + +For optimal performance: + +```typescript +// โœ… Efficient - direct property access +const techniques = attackDataModel.techniques; + +// โœ… Efficient - use built-in navigation +const tactics = technique.getTactics(); + +// โŒ Less efficient - repeated searches +const technique = attackDataModel.getAllObjects().find(/* ... */); +``` + +## Thread Safety + +The `AttackDataModel` is **read-only** after construction and safe for concurrent access across multiple threads or async operations. + +## Integration Patterns + +### Caching Data Models + +```typescript +const modelCache = new Map(); + +function getCachedModel(uuid: string): AttackDataModel { + if (!modelCache.has(uuid)) { + const model = loadDataModel(uuid); + modelCache.set(uuid, model); + } + return modelCache.get(uuid)!; +} +``` + +### Filtering and Searching + +```typescript +// Find techniques by platform +const windowsTechniques = attackDataModel.techniques.filter(t => + t.x_mitre_platforms?.includes('Windows') +); + +// Search by name +const credentialTechniques = attackDataModel.techniques.filter(t => + t.name.toLowerCase().includes('credential') +); + +// Complex filtering +const highValueTargets = attackDataModel.techniques.filter(t => { + const tactics = t.getTactics(); + return tactics.some(tactic => + tactic.x_mitre_shortname === 'credential-access' + ) && t.x_mitre_platforms?.includes('Windows'); +}); +``` + +--- diff --git a/docusaurus/docs/reference/api/data-sources.mdx b/docusaurus/docs/reference/api/data-sources.mdx new file mode 100644 index 00000000..7a81b017 --- /dev/null +++ b/docusaurus/docs/reference/api/data-sources.mdx @@ -0,0 +1,425 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# DataSource + + + +**Data source configuration and registration for loading ATT&CK datasets** + +The `DataSource` class defines where and how to load ATT&CK data. It supports multiple source types including the official ATT&CK repository, local files, remote URLs, and TAXII servers. + +## Constructor + +```typescript +new DataSource(options: DataSourceOptions) +``` + +### DataSourceOptions Interface + +```typescript +interface DataSourceOptions { + source: 'attack' | 'file' | 'url' | 'taxii'; + parsingMode?: 'strict' | 'relaxed'; + + // Attack source options + domain?: 'enterprise-attack' | 'mobile-attack' | 'ics-attack'; + version?: string; + + // File source options + file?: string; + + // URL source options + url?: string; + timeout?: number; + headers?: Record; + + // TAXII source options (coming soon) + server?: string; + collection?: string; + credentials?: { + username: string; + password: string; + }; +} +``` + +## Source Types + +### Attack Repository Source + +Load data from the official MITRE ATT&CK STIX 2.1 repository. + +```typescript +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Default | Description | +|--------|------|----------|---------|-------------| +| `source` | `'attack'` | โœ… | - | Specifies ATT&CK repository source | +| `domain` | `'enterprise-attack'` \| `'mobile-attack'` \| `'ics-attack'` | โœ… | - | ATT&CK domain to load | +| `version` | `string` | โŒ | `'latest'` | Specific version (e.g., '15.1') or 'latest' | +| `parsingMode` | `'strict'` \| `'relaxed'` | โŒ | `'strict'` | Validation strictness | + +**Available Versions**: + +- `'latest'` - Most recent release +- `'15.1'` - Version 15.1 (latest stable) +- `'15.0'` - Version 15.0 +- See [ATT&CK releases](https://github.com/mitre-attack/attack-stix-data/releases) for all versions + +**Domain Content**: + +- `enterprise-attack` - Techniques for Windows, Linux, macOS, containers, cloud +- `mobile-attack` - Mobile-specific techniques for Android and iOS +- `ics-attack` - Industrial Control Systems techniques + +### File Source + +Load data from local STIX 2.1 bundle files. + +```typescript +const dataSource = new DataSource({ + source: 'file', + file: '/path/to/enterprise-attack.json', + parsingMode: 'relaxed' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `source` | `'file'` | โœ… | Specifies file source | +| `file` | `string` | โœ… | Absolute or relative path to STIX bundle JSON file | +| `parsingMode` | `'strict'` \| `'relaxed'` | โŒ | Validation strictness (default: 'strict') | + +**File Requirements**: + +- Must be valid JSON format +- Must contain a STIX 2.1 bundle with `type: "bundle"` +- Should include ATT&CK objects with proper schemas + +**Example File Structure**: + +```json +{ + "type": "bundle", + "id": "bundle--12345678-1234-1234-1234-123456789012", + "objects": [ + { + "type": "attack-pattern", + "id": "attack-pattern--...", + "name": "Process Injection", + // ... other technique properties + } + // ... more objects + ] +} +``` + +### URL Source + +Load data from remote URLs serving STIX 2.1 content. + +```typescript +const dataSource = new DataSource({ + source: 'url', + url: 'https://example.com/attack-data.json', + timeout: 30000, + headers: { + 'Authorization': 'Bearer token123', + 'Accept': 'application/json' + }, + parsingMode: 'strict' +}); +``` + +**Configuration Options**: + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `source` | `'url'` | โœ… | Specifies URL source | +| `url` | `string` | โœ… | HTTP/HTTPS URL to STIX bundle | +| `timeout` | `number` | โŒ | Request timeout in milliseconds (default: 10000) | +| `headers` | `Record` | โŒ | HTTP headers for authentication/content negotiation | +| `parsingMode` | `'strict'` \| `'relaxed'` | โŒ | Validation strictness (default: 'strict') | + +**URL Requirements**: + +- Must be accessible via HTTP/HTTPS +- Should return JSON content with proper MIME type +- Must contain valid STIX 2.1 bundle + +### TAXII Source (Coming Soon) + +Load data from TAXII 2.1 servers. + +```typescript +const dataSource = new DataSource({ + source: 'taxii', + server: 'https://cti-taxii.mitre.org', + collection: 'attack-patterns', + credentials: { + username: 'user', + password: 'pass' + } +}); +``` + +> **Note**: TAXII source support is planned for future releases. + +## Parsing Modes + +### Strict Mode + +```typescript +parsingMode: 'strict' +``` + +**Behavior**: + +- All objects must pass schema validation +- Any validation failure aborts the entire loading process +- Relationships must reference valid objects +- Recommended for production use with trusted data sources + +**Use Cases**: + +- Production applications requiring data integrity +- Applications with strict compliance requirements +- Testing and validation scenarios + +### Relaxed Mode + +```typescript +parsingMode: 'relaxed' +``` + +**Behavior**: + +- Invalid objects are logged but skipped +- Loading continues even with validation errors +- Broken relationships are ignored +- Useful for experimental or incomplete datasets + +**Use Cases**: + +- Development and testing with custom data +- Loading datasets with known minor issues +- Research scenarios with experimental data + +## Registration and Loading + +### registerDataSource() + +Validates and registers a data source for use. + +```typescript +async function registerDataSource(dataSource: DataSource): Promise +``` + +**Parameters**: + +- `dataSource` - Configured DataSource instance + +**Returns**: + +- `string` - UUID for the registered data source on success +- `null` - Registration failed + +**Example**: + +```typescript +import { registerDataSource } from '@mitre-attack/attack-data-model'; + +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +try { + const uuid = await registerDataSource(dataSource); + if (uuid) { + console.log(`Data source registered: ${uuid}`); + } else { + console.error('Registration failed'); + } +} catch (error) { + console.error('Registration error:', error); +} +``` + +### loadDataModel() + +Loads a previously registered data source. + +```typescript +function loadDataModel(uuid: string): AttackDataModel +``` + +**Parameters**: + +- `uuid` - UUID returned from `registerDataSource()` + +**Returns**: + +- `AttackDataModel` - Populated data model instance + +**Throws**: + +- Error if UUID is invalid or data source not registered + +**Example**: + +```typescript +import { loadDataModel } from '@mitre-attack/attack-data-model'; + +const attackDataModel = loadDataModel(uuid); +console.log(`Loaded ${attackDataModel.techniques.length} techniques`); +``` + +## Data Source Validation + +During registration, data sources undergo validation: + +### Network Sources (attack, url) + +1. **Connectivity Check** - Verify the source is accessible +2. **Content Validation** - Ensure valid JSON and STIX structure +3. **Schema Compliance** - Validate ATT&CK objects against schemas +4. **Relationship Integrity** - Check all relationships reference valid objects + +### File Sources + +1. **File Existence** - Verify file exists and is readable +2. **JSON Parsing** - Ensure valid JSON format +3. **Bundle Structure** - Validate STIX bundle format +4. **Content Validation** - Same as network sources + +## Caching and Performance + +### Automatic Caching + +Registered data sources are cached in memory for fast repeated access: + +```typescript +// First registration - downloads and validates +const uuid1 = await registerDataSource(dataSource); + +// Subsequent loads - uses cached data +const model1 = loadDataModel(uuid1); +const model2 = loadDataModel(uuid1); // Fast - from cache +``` + +### Cache Management + +```typescript +// Check if a data source is cached +import { isDataSourceCached } from '@mitre-attack/attack-data-model'; + +if (isDataSourceCached(uuid)) { + console.log('Data source is cached'); +} + +// Clear cache (if needed) +import { clearDataSourceCache } from '@mitre-attack/attack-data-model'; +clearDataSourceCache(uuid); +``` + +## Error Handling + +### Common Errors + +| Error Type | Cause | Solution | +|------------|-------|----------| +| `NetworkError` | URL unreachable, timeout | Check network connectivity, increase timeout | +| `FileNotFoundError` | File path invalid | Verify file exists and is readable | +| `ValidationError` | Invalid STIX data | Fix data format or use relaxed mode | +| `AuthenticationError` | Invalid credentials | Check username/password for TAXII sources | + +### Error Examples + +```typescript +try { + const uuid = await registerDataSource(dataSource); +} catch (error) { + if (error.message.includes('ENOTFOUND')) { + console.error('Network error: Check internet connection'); + } else if (error.message.includes('timeout')) { + console.error('Request timeout: Try increasing timeout value'); + } else if (error.message.includes('validation')) { + console.error('Data validation failed: Check data format'); + } else { + console.error('Unknown error:', error); + } +} +``` + +## Configuration Examples + +### Complete Examples + +**Production Enterprise Setup**: + +```typescript +const productionSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +**Development with Local Data**: + +```typescript +const devSource = new DataSource({ + source: 'file', + file: './data/custom-attack.json', + parsingMode: 'relaxed' +}); +``` + +**Remote Data with Authentication**: + +```typescript +const remoteSource = new DataSource({ + source: 'url', + url: 'https://api.example.com/attack/data', + timeout: 60000, + headers: { + 'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOi...', + 'Accept': 'application/stix+json' + }, + parsingMode: 'strict' +}); +``` + +**Multi-Domain Loading**: + +```typescript +const domains = ['enterprise-attack', 'mobile-attack', 'ics-attack']; +const dataSources = domains.map(domain => new DataSource({ + source: 'attack', + domain: domain as any, + version: 'latest' +})); + +const registrations = await Promise.all( + dataSources.map(ds => registerDataSource(ds)) +); + +const models = registrations + .filter(uuid => uuid !== null) + .map(uuid => loadDataModel(uuid!)); +``` + +--- diff --git a/docusaurus/docs/reference/api/index.mdx b/docusaurus/docs/reference/api/index.mdx new file mode 100644 index 00000000..d6e70656 --- /dev/null +++ b/docusaurus/docs/reference/api/index.mdx @@ -0,0 +1,172 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# API Reference + + + +**Complete documentation for all ATT&CK Data Model classes and functions** + +The ATT&CK Data Model provides a comprehensive TypeScript API for working with MITRE ATT&CK data. This reference section documents all public classes, methods, and functions available in the library. + +## Core API Components + +### [AttackDataModel Class](./attack-data-model) + +The main data model class that provides access to all ATT&CK objects and their relationships. + +**Key Features:** + +- Access to techniques, tactics, groups, campaigns, and other ATT&CK objects +- Automatic relationship mapping between objects +- Type-safe collections with full TypeScript support +- Memory-efficient caching and indexing + +### [Data Source Management](./data-sources) + +Functions and classes for registering and managing ATT&CK data sources. + +**Key Features:** + +- Support for official ATT&CK repository, local files, and URLs +- Configurable parsing modes (strict/relaxed) +- Data source validation and caching +- Version management and compatibility checking + +### [Utility Functions](./utilities) + +Helper functions for common operations and data processing tasks. + +**Key Features:** + +- Data validation and transformation utilities +- Relationship traversal helpers +- Error handling and logging utilities +- Performance optimization helpers + +## Object Type Categories + +### STIX Domain Objects (SDO) + +**Techniques and Tactics:** + +- `TechniqueImpl` - Individual ATT&CK techniques +- `TacticImpl` - ATT&CK tactics and kill chain phases + +**Threat Actors and Campaigns:** + +- `GroupImpl` - Threat actor groups and APTs +- `CampaignImpl` - Threat campaigns and operations + +**Software and Tools:** + +- `MalwareImpl` - Malicious software used by threat actors +- `ToolImpl` - Tools and utilities used in attacks + +**Detection and Mitigation:** + +- `MitigationImpl` - Defensive measures and countermeasures +- `DataSourceImpl` - Detection data sources +- `DataComponentImpl` - Specific detection methods + +**Infrastructure and Assets:** + +- `AssetImpl` - Systems and infrastructure components +- `IdentityImpl` - Organizations and identity information +- `MatrixImpl` - ATT&CK framework matrices + +### STIX Relationship Objects (SRO) + +**Relationships:** + +- `RelationshipImpl` - Links between ATT&CK objects + +### STIX Meta Objects (SMO) + +**Metadata:** + +- `MarkingDefinitionImpl` - Data marking and classification + +## Common Usage Patterns + +### Loading Data Models + +```typescript +import { registerDataSource, loadDataModel, DataSource } from '@mitre-attack/attack-data-model'; + +// Register a data source +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1' +}); + +const uuid = await registerDataSource(dataSource); +const attackDataModel = loadDataModel(uuid); +``` + +### Accessing ATT&CK Objects + +```typescript +// Get all techniques +const techniques = attackDataModel.techniques; + +// Get all tactics +const tactics = attackDataModel.tactics; + +// Get all groups +const groups = attackDataModel.groups; +``` + +### Relationship Navigation + +```typescript +// Get techniques used by a group +const group = attackDataModel.groups[0]; +const techniquesUsed = group.getTechniques(); + +// Get tactics for a technique +const technique = attackDataModel.techniques[0]; +const tactics = technique.getTactics(); + +// Get mitigations for a technique +const mitigations = technique.getMitigations(); +``` + +### Schema Validation + +```typescript +import { techniqueSchema } from '@mitre-attack/attack-data-model'; + +try { + const validTechnique = techniqueSchema.parse(rawTechniqueData); + console.log('Validation successful:', validTechnique.name); +} catch (error) { + console.error('Validation failed:', error); +} +``` + +## Error Handling + +All API functions and methods use consistent error handling patterns: + +**Validation Errors:** Zod validation errors with detailed field-level information +**Data Source Errors:** Network, file system, or data format errors +**Relationship Errors:** Missing or invalid relationship references + +See the [Error Reference](../errors) for complete error code documentation. + +## Performance Considerations + +**Memory Usage:** The library loads entire datasets into memory for optimal query performance +**Initialization Time:** Initial data loading and relationship mapping takes time proportional to dataset size +**Query Performance:** Object access and relationship traversal are optimized for speed after initialization + +## TypeScript Integration + +The library is designed for optimal TypeScript experience: + +**Full Type Safety:** All objects, properties, and methods are fully typed +**IDE Support:** Auto-completion, inline documentation, and error detection +**Generic Support:** Type parameters for custom extensions and filtering + +--- diff --git a/docusaurus/docs/reference/api/utilities.mdx b/docusaurus/docs/reference/api/utilities.mdx new file mode 100644 index 00000000..52986f9c --- /dev/null +++ b/docusaurus/docs/reference/api/utilities.mdx @@ -0,0 +1,598 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Utility Functions + + + +**Helper functions and data manipulation tools** + +The ATT&CK Data Model provides various utility functions for common data manipulation, validation, and analysis tasks. These functions complement the main classes and provide convenient ways to work with ATT&CK data. + +## Data Source Utilities + +### isDataSourceCached() + +Check if a data source is currently cached in memory. + +```typescript +function isDataSourceCached(uuid: string): boolean +``` + +**Parameters**: + +- `uuid` - Data source UUID from registration + +**Returns**: `true` if cached, `false` otherwise + +**Example**: + +```typescript +import { isDataSourceCached } from '@mitre-attack/attack-data-model'; + +if (isDataSourceCached(uuid)) { + console.log('Data source is cached - loading will be fast'); +} else { + console.log('Data source will be loaded from storage'); +} +``` + +### clearDataSourceCache() + +Remove a specific data source from the cache. + +```typescript +function clearDataSourceCache(uuid: string): boolean +``` + +**Parameters**: + +- `uuid` - Data source UUID to remove from cache + +**Returns**: `true` if cache was cleared, `false` if UUID not found + +**Example**: + +```typescript +import { clearDataSourceCache } from '@mitre-attack/attack-data-model'; + +const cleared = clearDataSourceCache(uuid); +if (cleared) { + console.log('Cache cleared - next load will refresh data'); +} +``` + +### clearAllDataSourceCaches() + +Remove all data sources from the cache. + +```typescript +function clearAllDataSourceCaches(): number +``` + +**Returns**: Number of data sources that were cached + +**Example**: + +```typescript +import { clearAllDataSourceCaches } from '@mitre-attack/attack-data-model'; + +const clearedCount = clearAllDataSourceCaches(); +console.log(`Cleared ${clearedCount} cached data sources`); +``` + +## Object Identification Utilities + +### isValidStixId() + +Validate STIX ID format compliance. + +```typescript +function isValidStixId(id: string): boolean +``` + +**Parameters**: + +- `id` - String to validate as STIX ID + +**Returns**: `true` if valid STIX ID format, `false` otherwise + +**Valid STIX ID Format**: `{type}--{UUID}` where UUID is version 4 + +**Example**: + +```typescript +import { isValidStixId } from '@mitre-attack/attack-data-model'; + +console.log(isValidStixId('attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298')); // true +console.log(isValidStixId('invalid-id')); // false +console.log(isValidStixId('attack-pattern--not-a-uuid')); // false +``` + +### isValidAttackId() + +Validate ATT&CK ID format for specific object types. + +```typescript +function isValidAttackId(id: string, objectType?: string): boolean +``` + +**Parameters**: + +- `id` - String to validate as ATT&CK ID +- `objectType` - Optional STIX object type for specific validation + +**Returns**: `true` if valid ATT&CK ID format, `false` otherwise + +**Supported Formats by Object Type**: + +| Object Type | ATT&CK ID Format | Example | +|-------------|------------------|---------| +| `attack-pattern` | `T\d{4}(\.\d{3})?` | `T1055`, `T1055.001` | +| `x-mitre-tactic` | `TA\d{4}` | `TA0001` | +| `intrusion-set` | `G\d{4}` | `G0006` | +| `malware` | `S\d{4}` | `S0001` | +| `tool` | `S\d{4}` | `S0002` | +| `campaign` | `C\d{4}` | `C0001` | +| `course-of-action` | `M\d{4}` | `M1001` | + +**Example**: + +```typescript +import { isValidAttackId } from '@mitre-attack/attack-data-model'; + +// Generic validation +console.log(isValidAttackId('T1055')); // true +console.log(isValidAttackId('G0006')); // true +console.log(isValidAttackId('invalid')); // false + +// Type-specific validation +console.log(isValidAttackId('T1055.001', 'attack-pattern')); // true +console.log(isValidAttackId('G0006', 'attack-pattern')); // false +console.log(isValidAttackId('TA0001', 'x-mitre-tactic')); // true +``` + +### extractAttackId() + +Extract ATT&CK ID from an object's external references. + +```typescript +function extractAttackId(obj: AttackObject): string | null +``` + +**Parameters**: + +- `obj` - ATT&CK object with external references + +**Returns**: ATT&CK ID if found, `null` otherwise + +**Example**: + +```typescript +import { extractAttackId } from '@mitre-attack/attack-data-model'; + +const technique = attackDataModel.techniques[0]; +const attackId = extractAttackId(technique); +console.log(`Technique ID: ${attackId}`); // "T1055" +``` + +### extractStixType() + +Extract the STIX object type from a STIX ID. + +```typescript +function extractStixType(stixId: string): string | null +``` + +**Parameters**: + +- `stixId` - Valid STIX ID + +**Returns**: Object type if valid, `null` otherwise + +**Example**: + +```typescript +import { extractStixType } from '@mitre-attack/attack-data-model'; + +const type = extractStixType('attack-pattern--12345678-1234-1234-1234-123456789012'); +console.log(type); // "attack-pattern" + +const invalidType = extractStixType('invalid-id'); +console.log(invalidType); // null +``` + +## Data Analysis Utilities + +### getObjectCounts() + +Get counts of all object types in a data model. + +```typescript +function getObjectCounts(dataModel: AttackDataModel): Record +``` + +**Parameters**: + +- `dataModel` - AttackDataModel instance + +**Returns**: Object with counts by STIX object type + +**Example**: + +```typescript +import { getObjectCounts } from '@mitre-attack/attack-data-model'; + +const counts = getObjectCounts(attackDataModel); +console.log(counts); +// { +// "attack-pattern": 196, +// "x-mitre-tactic": 14, +// "intrusion-set": 142, +// "malware": 89, +// "tool": 76, +// "relationship": 2341 +// } +``` + +### findBrokenRelationships() + +Identify relationships that reference non-existent objects. + +```typescript +function findBrokenRelationships(dataModel: AttackDataModel): BrokenRelationship[] +``` + +**Returns**: Array of broken relationship information + +**BrokenRelationship Interface**: + +```typescript +interface BrokenRelationship { + relationshipId: string; + relationshipType: string; + sourceId: string; + targetId: string; + missingReference: 'source' | 'target'; + description?: string; +} +``` + +**Example**: + +```typescript +import { findBrokenRelationships } from '@mitre-attack/attack-data-model'; + +const broken = findBrokenRelationships(attackDataModel); +if (broken.length > 0) { + console.log(`Found ${broken.length} broken relationships:`); + broken.forEach(rel => { + console.log(`- ${rel.relationshipType} missing ${rel.missingReference}`); + }); +} else { + console.log('All relationships are valid'); +} +``` + +### getRelationshipCounts() + +Count relationships by type. + +```typescript +function getRelationshipCounts(dataModel: AttackDataModel): Record +``` + +**Parameters**: + +- `dataModel` - AttackDataModel instance + +**Returns**: Object with counts by relationship type + +**Example**: + +```typescript +import { getRelationshipCounts } from '@mitre-attack/attack-data-model'; + +const relCounts = getRelationshipCounts(attackDataModel); +console.log(relCounts); +// { +// "uses": 1824, +// "mitigates": 389, +// "subtechnique-of": 198, +// "attributed-to": 67, +// "detects": 156 +// } +``` + +## Filtering and Search Utilities + +### filterByPlatform() + +Filter techniques by platform. + +```typescript +function filterByPlatform( + techniques: TechniqueImpl[], + platform: string | string[] +): TechniqueImpl[] +``` + +**Parameters**: + +- `techniques` - Array of techniques to filter +- `platform` - Platform name(s) to match + +**Returns**: Filtered array of techniques + +**Example**: + +```typescript +import { filterByPlatform } from '@mitre-attack/attack-data-model'; + +const windowsTechniques = filterByPlatform( + attackDataModel.techniques, + 'Windows' +); + +const multiPlatform = filterByPlatform( + attackDataModel.techniques, + ['Windows', 'Linux'] +); + +console.log(`Windows techniques: ${windowsTechniques.length}`); +console.log(`Multi-platform techniques: ${multiPlatform.length}`); +``` + +### filterByTactic() + +Filter techniques by associated tactic. + +```typescript +function filterByTactic( + techniques: TechniqueImpl[], + tacticShortname: string | string[] +): TechniqueImpl[] +``` + +**Parameters**: + +- `techniques` - Array of techniques to filter +- `tacticShortname` - Tactic shortname(s) to match + +**Returns**: Filtered array of techniques + +**Example**: + +```typescript +import { filterByTactic } from '@mitre-attack/attack-data-model'; + +const credAccessTechniques = filterByTactic( + attackDataModel.techniques, + 'credential-access' +); + +const multiTactic = filterByTactic( + attackDataModel.techniques, + ['initial-access', 'execution'] +); + +console.log(`Credential Access techniques: ${credAccessTechniques.length}`); +``` + +### searchByName() + +Search objects by name with fuzzy matching. + +```typescript +function searchByName( + objects: T[], + searchTerm: string, + options?: SearchOptions +): T[] +``` + +**SearchOptions Interface**: + +```typescript +interface SearchOptions { + caseSensitive?: boolean; + fuzzyMatch?: boolean; + maxResults?: number; +} +``` + +**Parameters**: + +- `objects` - Array of objects to search +- `searchTerm` - Text to search for +- `options` - Search configuration + +**Returns**: Array of matching objects + +**Example**: + +```typescript +import { searchByName } from '@mitre-attack/attack-data-model'; + +// Case-insensitive exact match +const credentialTechniques = searchByName( + attackDataModel.techniques, + 'credential', + { caseSensitive: false, maxResults: 10 } +); + +// Fuzzy matching +const processMatches = searchByName( + attackDataModel.techniques, + 'proces', + { fuzzyMatch: true, maxResults: 5 } +); + +console.log(`Found ${credentialTechniques.length} credential techniques`); +``` + +## Validation Utilities + +### validateBundle() + +Validate a STIX bundle structure. + +```typescript +function validateBundle(bundle: unknown): ValidationResult +``` + +**ValidationResult Interface**: + +```typescript +interface ValidationResult { + isValid: boolean; + errors: ValidationError[]; + warnings: string[]; + objectCounts: Record; +} +``` + +**Parameters**: + +- `bundle` - Bundle data to validate + +**Returns**: Validation result with details + +**Example**: + +```typescript +import { validateBundle } from '@mitre-attack/attack-data-model'; +import fs from 'fs'; + +const bundleData = JSON.parse(fs.readFileSync('bundle.json', 'utf8')); +const result = validateBundle(bundleData); + +if (result.isValid) { + console.log('โœ… Bundle is valid'); + console.log('Object counts:', result.objectCounts); +} else { + console.log('โŒ Bundle validation failed'); + result.errors.forEach(error => { + console.log(`- ${error.message}`); + }); +} +``` + +### validateRelationshipIntegrity() + +Check that all relationships reference valid objects. + +```typescript +function validateRelationshipIntegrity(dataModel: AttackDataModel): IntegrityResult +``` + +**IntegrityResult Interface**: + +```typescript +interface IntegrityResult { + isValid: boolean; + brokenRelationships: BrokenRelationship[]; + orphanedObjects: string[]; + totalRelationships: number; +} +``` + +**Example**: + +```typescript +import { validateRelationshipIntegrity } from '@mitre-attack/attack-data-model'; + +const integrity = validateRelationshipIntegrity(attackDataModel); + +console.log(`Total relationships: ${integrity.totalRelationships}`); +console.log(`Broken relationships: ${integrity.brokenRelationships.length}`); +console.log(`Orphaned objects: ${integrity.orphanedObjects.length}`); + +if (integrity.isValid) { + console.log('โœ… All relationships are valid'); +} else { + console.log('โŒ Found integrity issues'); +} +``` + +## Export Utilities + +### exportToJson() + +Export data model to JSON format. + +```typescript +function exportToJson( + dataModel: AttackDataModel, + options?: ExportOptions +): string +``` + +**ExportOptions Interface**: + +```typescript +interface ExportOptions { + pretty?: boolean; + includeRelationships?: boolean; + objectTypes?: string[]; + minify?: boolean; +} +``` + +**Example**: + +```typescript +import { exportToJson } from '@mitre-attack/attack-data-model'; + +// Pretty-printed with all data +const fullExport = exportToJson(attackDataModel, { + pretty: true, + includeRelationships: true +}); + +// Minified with only techniques +const techniquesOnly = exportToJson(attackDataModel, { + minify: true, + objectTypes: ['attack-pattern'] +}); + +fs.writeFileSync('full-export.json', fullExport); +fs.writeFileSync('techniques.json', techniquesOnly); +``` + +### exportToCsv() + +Export specific object collections to CSV format. + +```typescript +function exportToCsv( + objects: AttackObject[], + fields?: string[] +): string +``` + +**Parameters**: + +- `objects` - Array of objects to export +- `fields` - Specific fields to include (optional) + +**Returns**: CSV-formatted string + +**Example**: + +```typescript +import { exportToCsv } from '@mitre-attack/attack-data-model'; + +// Export all technique fields +const techniquesCsv = exportToCsv(attackDataModel.techniques); + +// Export specific fields only +const basicTechniquesCsv = exportToCsv( + attackDataModel.techniques, + ['name', 'external_references', 'x_mitre_platforms'] +); + +fs.writeFileSync('techniques.csv', techniquesCsv); +fs.writeFileSync('techniques-basic.csv', basicTechniquesCsv); +``` + +--- diff --git a/docusaurus/docs/reference/configuration.mdx b/docusaurus/docs/reference/configuration.mdx new file mode 100644 index 00000000..9b6de9e8 --- /dev/null +++ b/docusaurus/docs/reference/configuration.mdx @@ -0,0 +1,524 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Configuration Reference + + + +**Complete configuration options for all data source types** + +This reference documents all configuration parameters available when creating `DataSource` instances and initializing the ATT&CK Data Model library. + +## DataSource Configuration + +### Base Configuration + +All data sources require these base configuration options: + +```typescript +interface DataSourceOptions { + source: 'attack' | 'file' | 'url' | 'taxii'; + parsingMode?: 'strict' | 'relaxed'; +} +``` + +#### source + +**Type**: `'attack' | 'file' | 'url' | 'taxii'` +**Required**: โœ… +**Description**: Specifies the type of data source to load + +| Value | Description | +|-------|-------------| +| `'attack'` | Official MITRE ATT&CK STIX 2.1 repository | +| `'file'` | Local STIX bundle JSON file | +| `'url'` | Remote URL serving STIX content | +| `'taxii'` | TAXII 2.1 server *(coming soon)* | + +#### parsingMode + +**Type**: `'strict' | 'relaxed'` +**Required**: โŒ +**Default**: `'strict'` +**Description**: Controls validation behavior during data parsing + +| Mode | Behavior | +|------|----------| +| `'strict'` | All objects must pass validation; failures abort loading | +| `'relaxed'` | Invalid objects are logged but skipped; loading continues | + +**Recommendations**: + +- Use `'strict'` for production applications with trusted data sources +- Use `'relaxed'` for development, testing, or experimental data + +## Attack Source Configuration + +Configuration for loading official MITRE ATT&CK data. + +```typescript +interface AttackSourceOptions extends DataSourceOptions { + source: 'attack'; + domain: 'enterprise-attack' | 'mobile-attack' | 'ics-attack'; + version?: string; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'strict' +}); +``` + +### Attack-Specific Options + +#### domain + +**Type**: `'enterprise-attack' | 'mobile-attack' | 'ics-attack'` +**Required**: โœ… +**Description**: ATT&CK domain to load + +| Domain | Description | Object Count (Approx.) | +|--------|-------------|------------------------| +| `'enterprise-attack'` | Enterprise techniques for Windows, Linux, macOS, containers, cloud | 600+ techniques, 14 tactics | +| `'mobile-attack'` | Mobile-specific techniques for Android and iOS | 100+ techniques, 14 tactics | +| `'ics-attack'` | Industrial Control Systems techniques | 80+ techniques, 11 tactics | + +#### version + +**Type**: `string` +**Required**: โŒ +**Default**: `'latest'` +**Description**: Specific ATT&CK version or 'latest' for most recent release + +**Available Versions**: + +- `'latest'` - Always loads the most recent release +- `'15.1'` - ATT&CK v15.1 (current stable) +- `'15.0'` - ATT&CK v15.0 +- `'14.1'` - ATT&CK v14.1 +- See [ATT&CK Releases](https://github.com/mitre-attack/attack-stix-data/releases) for all versions + +**Version Selection Guidelines**: + +- **Production**: Use specific version (e.g., `'15.1'`) for stability +- **Development**: Use `'latest'` for newest features +- **Research**: Use specific version for reproducible results + +## File Source Configuration + +Configuration for loading local STIX bundle files. + +```typescript +interface FileSourceOptions extends DataSourceOptions { + source: 'file'; + file: string; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'file', + file: '/path/to/enterprise-attack.json', + parsingMode: 'relaxed' +}); +``` + +### File-Specific Options + +#### file + +**Type**: `string` +**Required**: โœ… +**Description**: Path to STIX bundle JSON file (absolute or relative) + +**Path Resolution**: + +- Relative paths are resolved from current working directory +- Use absolute paths for guaranteed location +- File must be readable by the Node.js process + +**File Requirements**: + +```json +{ + "type": "bundle", + "id": "bundle--12345678-1234-1234-1234-123456789012", + "objects": [ + // Array of STIX objects + ] +} +``` + +**Common File Locations**: + +```typescript +// Relative to project root +file: './data/custom-attack.json' + +// Absolute path +file: '/var/data/attack/enterprise.json' + +// User home directory +file: path.join(os.homedir(), 'attack-data', 'bundle.json') +``` + +## URL Source Configuration + +Configuration for loading remote STIX bundles over HTTP/HTTPS. + +```typescript +interface UrlSourceOptions extends DataSourceOptions { + source: 'url'; + url: string; + timeout?: number; + headers?: Record; + retryAttempts?: number; + retryDelay?: number; +} +``` + +### Complete Example + +```typescript +const dataSource = new DataSource({ + source: 'url', + url: 'https://api.example.com/attack/enterprise.json', + timeout: 30000, + headers: { + 'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOi...', + 'Accept': 'application/json', + 'User-Agent': 'MyApp/1.0 ATT&CK-Data-Model' + }, + retryAttempts: 3, + retryDelay: 1000, + parsingMode: 'strict' +}); +``` + +### URL-Specific Options + +#### url + +**Type**: `string` +**Required**: โœ… +**Description**: HTTP/HTTPS URL to STIX bundle resource + +**URL Requirements**: + +- Must be accessible via HTTP or HTTPS +- Should return JSON content with appropriate MIME type +- Must contain valid STIX 2.1 bundle + +#### timeout + +**Type**: `number` +**Required**: โŒ +**Default**: `10000` (10 seconds) +**Description**: Request timeout in milliseconds + +**Timeout Guidelines**: + +- **Small datasets** (< 1 MB): 10-30 seconds +- **Large datasets** (> 10 MB): 60-300 seconds +- **Slow networks**: Increase proportionally + +#### headers + +**Type**: `Record` +**Required**: โŒ +**Description**: HTTP headers for authentication and content negotiation + +**Common Headers**: + +```typescript +{ + // Authentication + 'Authorization': 'Bearer ', + 'X-API-Key': '', + + // Content negotiation + 'Accept': 'application/json', + 'Accept': 'application/stix+json', + + // User agent identification + 'User-Agent': 'MyApp/1.0 (contact@example.com)', + + // Custom headers + 'X-Custom-Header': 'value' +} +``` + +#### retryAttempts + +**Type**: `number` +**Required**: โŒ +**Default**: `3` +**Description**: Number of retry attempts on failure + +#### retryDelay + +**Type**: `number` +**Required**: โŒ +**Default**: `1000` (1 second) +**Description**: Base delay between retry attempts in milliseconds + +**Retry Logic**: Uses exponential backoff - each retry delay is multiplied by 2 + +## TAXII Source Configuration + +Configuration for loading data from TAXII 2.1 servers *(coming soon)*. + +```typescript +interface TaxiiSourceOptions extends DataSourceOptions { + source: 'taxii'; + server: string; + collection: string; + credentials?: { + username: string; + password: string; + }; + apiRoot?: string; + timeout?: number; +} +``` + +### Planned Example + +```typescript +const dataSource = new DataSource({ + source: 'taxii', + server: 'https://cti-taxii.mitre.org', + collection: 'enterprise-attack', + credentials: { + username: 'user', + password: 'pass' + }, + apiRoot: '/taxii2/', + timeout: 30000 +}); +``` + +> **Note**: TAXII source support is planned for future releases. + +## Global Configuration + +### Environment Variables + +Configure library behavior through environment variables: + +#### ATTACK_DEBUG + +**Type**: `'true' | 'false'` +**Default**: `'false'` +**Description**: Enable verbose debug logging + +```bash +export ATTACK_DEBUG=true +node app.js +``` + +#### ATTACK_CACHE_DIR + +**Type**: `string` +**Default**: `os.tmpdir()/attack-data-model` +**Description**: Directory for caching downloaded data + +```bash +export ATTACK_CACHE_DIR=/var/cache/attack-data +node app.js +``` + +#### ATTACK_CACHE_TTL + +**Type**: `number` +**Default**: `3600` (1 hour) +**Description**: Cache time-to-live in seconds + +```bash +export ATTACK_CACHE_TTL=7200 # 2 hours +node app.js +``` + +#### ATTACK_MAX_BUNDLE_SIZE + +**Type**: `number` +**Default**: `104857600` (100 MB) +**Description**: Maximum bundle size in bytes + +```bash +export ATTACK_MAX_BUNDLE_SIZE=209715200 # 200 MB +node app.js +``` + +### Programmatic Configuration + +Configure library behavior programmatically: + +```typescript +import { configure } from '@mitre-attack/attack-data-model'; + +configure({ + debug: true, + cacheDir: './data/cache', + cacheTtl: 3600, + maxBundleSize: 100 * 1024 * 1024, // 100 MB + userAgent: 'MyApp/1.0' +}); +``` + +#### Global Configuration Options + +```typescript +interface GlobalConfiguration { + debug?: boolean; + cacheDir?: string; + cacheTtl?: number; + maxBundleSize?: number; + userAgent?: string; + retryAttempts?: number; + retryDelay?: number; +} +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `debug` | `boolean` | `false` | Enable debug logging | +| `cacheDir` | `string` | OS temp dir | Cache directory path | +| `cacheTtl` | `number` | `3600` | Cache TTL in seconds | +| `maxBundleSize` | `number` | `104857600` | Max bundle size in bytes | +| `userAgent` | `string` | Library default | HTTP User-Agent header | +| `retryAttempts` | `number` | `3` | Default retry attempts | +| `retryDelay` | `number` | `1000` | Default retry delay (ms) | + +## Configuration Validation + +### Validation Rules + +The library validates configuration during `DataSource` creation: + +```typescript +// โœ… Valid configurations +const validConfigs = [ + { source: 'attack', domain: 'enterprise-attack' }, + { source: 'file', file: './data.json' }, + { source: 'url', url: 'https://example.com/data.json' } +]; + +// โŒ Invalid configurations +const invalidConfigs = [ + { source: 'attack' }, // Missing domain + { source: 'file' }, // Missing file path + { source: 'url' }, // Missing URL + { source: 'invalid' } // Invalid source type +]; +``` + +### Configuration Errors + +Common configuration errors and solutions: + +| Error | Cause | Solution | +|-------|-------|----------| +| `Missing required option: domain` | Attack source without domain | Add `domain` property | +| `Missing required option: file` | File source without path | Add `file` property | +| `Missing required option: url` | URL source without URL | Add `url` property | +| `Invalid source type` | Unknown source value | Use valid source type | +| `Invalid domain` | Unknown domain value | Use valid ATT&CK domain | + +## Best Practices + +### Production Configuration + +```typescript +// Recommended production setup +const productionSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', // Pin specific version + parsingMode: 'strict' // Enforce data quality +}); + +// With global configuration +configure({ + debug: false, // Disable debug in production + cacheDir: '/var/cache/attack', // Persistent cache location + cacheTtl: 86400, // 24 hour cache + userAgent: 'MyApp/2.1 (security-team@company.com)' +}); +``` + +### Development Configuration + +```typescript +// Recommended development setup +const devSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: 'latest', // Use latest for development + parsingMode: 'relaxed' // More forgiving validation +}); + +// With debug logging +configure({ + debug: true, // Enable detailed logging + cacheDir: './dev-cache', // Local cache directory + cacheTtl: 3600 // 1 hour cache for faster iteration +}); +``` + +### Performance Configuration + +```typescript +// For high-performance scenarios +const perfSource = new DataSource({ + source: 'url', + url: 'https://cdn.example.com/attack-data.json', + timeout: 60000, // Generous timeout + retryAttempts: 5, // More retries + retryDelay: 2000, // Longer retry delay + parsingMode: 'strict' +}); + +configure({ + maxBundleSize: 500 * 1024 * 1024, // 500 MB for large datasets + cacheTtl: 604800 // 7 day cache +}); +``` + +## Migration Guide + +### Upgrading from v1.x + +```typescript +// v1.x configuration +const oldConfig = { + dataSource: 'github', + attackVersion: '14.1', + strictMode: true +}; + +// v2.x equivalent +const newConfig = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '14.1', + parsingMode: 'strict' +}); +``` + +### Environment Variable Changes + +| v1.x Variable | v2.x Variable | Notes | +|---------------|---------------|-------| +| `ATT_DEBUG` | `ATTACK_DEBUG` | Renamed for consistency | +| `ATT_CACHE_PATH` | `ATTACK_CACHE_DIR` | Renamed and improved | +| `ATT_VERSION` | *(removed)* | Now per-DataSource configuration | + +--- diff --git a/docusaurus/docs/reference/errors.mdx b/docusaurus/docs/reference/errors.mdx new file mode 100644 index 00000000..37751f47 --- /dev/null +++ b/docusaurus/docs/reference/errors.mdx @@ -0,0 +1,387 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Error Reference + + + +**Complete error codes, types, and resolution strategies** + +This reference documents all error types that can occur when using the ATT&CK Data Model library, along with their meanings, common causes, and recommended solutions. + +## Error Categories + +### Data Source Errors + +Errors related to data source configuration, registration, and loading. + +#### DataSourceError + +**Type**: `DataSourceError` +**Thrown by**: `registerDataSource()`, data source validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `DS001` | `"Invalid data source type: {type}"` | Unsupported source type | Use 'attack', 'file', 'url', or 'taxii' | +| `DS002` | `"Missing required option: {option}"` | Required configuration missing | Provide required option for source type | +| `DS003` | `"Data source registration failed"` | General registration failure | Check configuration and network connectivity | +| `DS004` | `"Data source not found: {uuid}"` | Invalid UUID in loadDataModel() | Use valid UUID from registerDataSource() | +| `DS005` | `"Data source already registered: {uuid}"` | Duplicate registration attempt | Use existing UUID or clear cache first | + +**Example**: + +```typescript +// โŒ Throws DS001 - Invalid data source type +const dataSource = new DataSource({ source: 'invalid' }); + +// โŒ Throws DS002 - Missing required domain +const dataSource = new DataSource({ source: 'attack' }); + +// โœ… Correct configuration +const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack' +}); +``` + +#### NetworkError + +**Type**: `NetworkError` +**Thrown by**: URL and attack source loading + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `NET001` | `"Network request failed: {url}"` | Network connectivity issues | Check internet connection | +| `NET002` | `"Request timeout: {url}"` | Request exceeded timeout | Increase timeout or check server status | +| `NET003` | `"Authentication failed: {url}"` | Invalid credentials | Verify authentication details | +| `NET004` | `"Resource not found: {url}"` | 404 or invalid URL | Verify URL exists and is accessible | +| `NET005` | `"Server error: {status} {url}"` | Server-side error (5xx) | Check server status or try again later | + +**Example**: + +```typescript +try { + const dataSource = new DataSource({ + source: 'url', + url: 'https://invalid-url.example/data.json', + timeout: 5000 + }); + await registerDataSource(dataSource); +} catch (error) { + if (error.code === 'NET002') { + // Increase timeout and retry + dataSource.timeout = 30000; + } +} +``` + +#### FileSystemError + +**Type**: `FileSystemError` +**Thrown by**: File source loading + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `FS001` | `"File not found: {path}"` | File doesn't exist | Verify file path is correct | +| `FS002` | `"Permission denied: {path}"` | Insufficient file permissions | Check file permissions | +| `FS003` | `"File is not readable: {path}"` | File exists but can't be read | Ensure file is readable | +| `FS004` | `"Invalid file format: {path}"` | File is not valid JSON | Verify file contains valid JSON | + +### Validation Errors + +Errors from schema validation and data parsing. + +#### ValidationError + +**Type**: `ValidationError` (extends `ZodError`) +**Thrown by**: Schema parsing, object validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `VAL001` | `"Required field missing: {field}"` | Required property not provided | Add missing field to object | +| `VAL002` | `"Invalid field type: {field}"` | Wrong data type for field | Use correct type (string, number, array, etc.) | +| `VAL003` | `"Invalid field value: {field}"` | Value doesn't match constraints | Check field constraints and valid values | +| `VAL004` | `"Invalid STIX ID format: {id}"` | Malformed STIX identifier | Use format: `{type}--{uuid}` | +| `VAL005` | `"Invalid ATT&CK ID format: {id}"` | Malformed ATT&CK identifier | Follow ATT&CK ID patterns (T1234, G0001, etc.) | +| `VAL006` | `"Schema refinement failed: {details}"` | Custom validation rule failed | Check object meets all requirements | + +**Common Validation Issues**: + +```typescript +// โŒ VAL001 - Missing required field +{ + "type": "attack-pattern", + "id": "attack-pattern--12345...", + // Missing: name, description, created, modified, etc. +} + +// โŒ VAL002 - Invalid field type +{ + "type": "attack-pattern", + "name": 123, // Should be string + "x_mitre_platforms": "Windows" // Should be array +} + +// โŒ VAL004 - Invalid STIX ID +{ + "id": "not-a-valid-stix-id" +} + +// โœ… Valid object +{ + "type": "attack-pattern", + "id": "attack-pattern--12345678-1234-1234-1234-123456789012", + "spec_version": "2.1", + "created": "2023-01-01T00:00:00.000Z", + "modified": "2023-01-01T00:00:00.000Z", + "name": "Process Injection", + "description": "Adversaries may inject code...", + "x_mitre_attack_spec_version": "3.3.0", + "x_mitre_version": "1.0" +} +``` + +#### BundleValidationError + +**Type**: `BundleValidationError` +**Thrown by**: STIX bundle validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `BUN001` | `"Invalid bundle structure"` | Not a valid STIX bundle | Ensure `type: "bundle"` and objects array | +| `BUN002` | `"Bundle contains no objects"` | Empty objects array | Add STIX objects to bundle | +| `BUN003` | `"Duplicate object IDs in bundle"` | Same ID used multiple times | Ensure all object IDs are unique | +| `BUN004` | `"Invalid bundle ID format"` | Malformed bundle identifier | Use format: `bundle--{uuid}` | + +### Relationship Errors + +Errors from relationship processing and navigation. + +#### RelationshipError + +**Type**: `RelationshipError` +**Thrown by**: Relationship processing, navigation methods + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `REL001` | `"Invalid relationship type: {type}"` | Unknown relationship type | Use valid STIX relationship types | +| `REL002` | `"Source object not found: {id}"` | Relationship source doesn't exist | Ensure source object is in dataset | +| `REL003` | `"Target object not found: {id}"` | Relationship target doesn't exist | Ensure target object is in dataset | +| `REL004` | `"Circular relationship detected"` | Self-referencing relationship chain | Fix relationship structure | +| `REL005` | `"Invalid relationship direction"` | Wrong source/target for relationship type | Check relationship type requirements | + +**Example**: + +```typescript +// โŒ REL001 - Invalid relationship type +{ + "type": "relationship", + "relationship_type": "invalid-relationship", + "source_ref": "attack-pattern--...", + "target_ref": "x-mitre-tactic--..." +} + +// โœ… Valid relationship +{ + "type": "relationship", + "relationship_type": "uses", + "source_ref": "intrusion-set--...", + "target_ref": "attack-pattern--..." +} +``` + +### Parsing Errors + +Errors from data parsing and processing. + +#### ParsingError + +**Type**: `ParsingError` +**Thrown by**: Data parsing, format processing + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `PAR001` | `"Invalid JSON format"` | Malformed JSON data | Fix JSON syntax errors | +| `PAR002` | `"Unsupported data format"` | Non-JSON or unknown format | Use valid STIX JSON format | +| `PAR003` | `"Data too large to process"` | Bundle exceeds size limits | Split into smaller bundles or increase limits | +| `PAR004` | `"Encoding error: {encoding}"` | Unsupported character encoding | Use UTF-8 encoding | + +### Configuration Errors + +Errors from library configuration and setup. + +#### ConfigurationError + +**Type**: `ConfigurationError` +**Thrown by**: Library initialization, configuration validation + +| Error Code | Message Pattern | Cause | Solution | +|------------|-----------------|-------|----------| +| `CFG001` | `"Invalid configuration: {details}"` | Configuration object invalid | Check configuration structure | +| `CFG002` | `"Conflicting options: {options}"` | Mutually exclusive options set | Use only one of the conflicting options | +| `CFG003` | `"Environment not supported: {env}"` | Unsupported runtime environment | Use supported Node.js version | +| `CFG004` | `"Missing dependency: {dependency}"` | Required dependency not installed | Install missing dependencies | + +## Error Handling Patterns + +### Basic Error Handling + +```typescript +try { + const dataSource = new DataSource({ source: 'attack', domain: 'enterprise-attack' }); + const uuid = await registerDataSource(dataSource); + const attackDataModel = loadDataModel(uuid); +} catch (error) { + if (error instanceof ValidationError) { + console.error('Validation failed:', error.errors); + } else if (error instanceof NetworkError) { + console.error('Network issue:', error.message); + } else if (error instanceof FileSystemError) { + console.error('File system error:', error.message); + } else { + console.error('Unknown error:', error); + } +} +``` + +### Specific Error Code Handling + +```typescript +try { + // ... data loading code +} catch (error) { + switch (error.code) { + case 'NET002': // Request timeout + console.log('Request timed out, retrying with longer timeout...'); + // Implement retry logic + break; + + case 'VAL001': // Missing required field + console.error('Data validation failed - required field missing'); + // Log specific validation errors + break; + + case 'REL002': // Relationship target not found + console.warn('Relationship integrity issue - continuing in relaxed mode'); + // Use relaxed parsing mode + break; + + default: + console.error('Unhandled error:', error); + } +} +``` + +### Comprehensive Error Handler + +```typescript +class AttackErrorHandler { + static handle(error: unknown): { + severity: 'fatal' | 'warning' | 'info'; + message: string; + solution: string; + } { + if (error instanceof ValidationError) { + return { + severity: 'fatal', + message: `Validation failed: ${error.errors.length} errors`, + solution: 'Fix validation errors or use relaxed parsing mode' + }; + } + + if (error instanceof NetworkError) { + return { + severity: error.code === 'NET002' ? 'warning' : 'fatal', + message: `Network error: ${error.message}`, + solution: 'Check connectivity and retry with backoff' + }; + } + + if (error instanceof RelationshipError) { + return { + severity: 'warning', + message: `Relationship issue: ${error.message}`, + solution: 'Use relaxed mode or fix relationship integrity' + }; + } + + return { + severity: 'fatal', + message: `Unknown error: ${error}`, + solution: 'Check error details and library documentation' + }; + } +} + +// Usage +try { + // ... ATT&CK data operations +} catch (error) { + const errorInfo = AttackErrorHandler.handle(error); + console.log(`[${errorInfo.severity.toUpperCase()}] ${errorInfo.message}`); + console.log(`Solution: ${errorInfo.solution}`); +} +``` + +## Debugging Tips + +### Enable Detailed Logging + +```typescript +// Set environment variable for verbose logging +process.env.ATTACK_DEBUG = 'true'; + +// Or enable programmatically +import { setDebugMode } from '@mitre-attack/attack-data-model'; +setDebugMode(true); +``` + +### Validate Data Before Processing + +```typescript +import { validateBundle } from '@mitre-attack/attack-data-model'; + +// Validate bundle structure before registration +const result = validateBundle(bundleData); +if (!result.isValid) { + console.error('Bundle validation failed:'); + result.errors.forEach(error => console.error(`- ${error.message}`)); +} else { + // Safe to proceed with registration +} +``` + +### Check Data Source Status + +```typescript +import { getDataSourceStatus } from '@mitre-attack/attack-data-model'; + +const status = getDataSourceStatus(uuid); +console.log(`Data source status: ${status.state}`); +console.log(`Last updated: ${status.lastUpdated}`); +console.log(`Object counts:`, status.objectCounts); +``` + +## Recovery Strategies + +### Network Failures + +1. **Implement exponential backoff retry** +2. **Use cached data if available** +3. **Fall back to local data sources** +4. **Increase timeout values for slow connections** + +### Validation Failures + +1. **Use relaxed parsing mode for development** +2. **Fix specific validation errors identified** +3. **Update data to match current schema requirements** +4. **Use partial data loading if some objects are valid** + +### Relationship Issues + +1. **Use relaxed mode to ignore broken relationships** +2. **Validate relationship integrity before processing** +3. **Remove orphaned relationships from datasets** +4. **Update data sources to fix reference issues** + +--- diff --git a/docusaurus/docs/reference/index.mdx b/docusaurus/docs/reference/index.mdx new file mode 100644 index 00000000..245bfdce --- /dev/null +++ b/docusaurus/docs/reference/index.mdx @@ -0,0 +1,192 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Reference + + + +**Complete technical specifications and API documentation** + +This section provides comprehensive reference material for the ATT&CK Data Model library. All classes, methods, schemas, and configuration options are documented here with precise technical details. + +## API Documentation + +### Core Classes + +- **[AttackDataModel](./api/attack-data-model)** - Main data model containing all ATT&CK objects +- **[DataSource](./api/data-sources)** - Data source configuration and registration +- **[Utility Functions](./api/utilities)** - Helper functions and data manipulation tools + +### Implementation Classes (SDO) + +All STIX Domain Object implementations with relationship navigation methods: + +| Class | ATT&CK Object | Key Methods | +|-------|---------------|-------------| +| `TechniqueImpl` | Techniques | `getTactics()`, `getSubtechniques()`, `getParentTechnique()`, `getMitigations()` | +| `TacticImpl` | Tactics | `getTechniques()` | +| `GroupImpl` | Groups/Intrusion Sets | `getTechniques()`, `getAssociatedSoftware()`, `getAssociatedCampaigns()` | +| `CampaignImpl` | Campaigns | `getTechniques()`, `getSoftware()`, `getAttributedTo()` | +| `MalwareImpl` | Malware | `getTechniques()`, `getAssociatedGroups()` | +| `ToolImpl` | Tools | `getTechniques()`, `getAssociatedGroups()` | +| `MitigationImpl` | Mitigations | `getTechniques()` | + +## Schema Documentation + +### STIX Domain Objects (SDO) + +Auto-generated documentation for all ATT&CK object schemas: + +- **[Techniques](./schemas/sdo/technique.schema)** - Attack patterns including sub-techniques +- **[Tactics](./schemas/sdo/tactic.schema)** - Adversary tactical goals +- **[Groups](./schemas/sdo/group.schema)** - Threat actor groups and intrusion sets +- **[Malware](./schemas/sdo/malware.schema)** - Malicious software +- **[Tools](./schemas/sdo/tool.schema)** - Legitimate software used by adversaries +- **[Campaigns](./schemas/sdo/campaign.schema)** - Coordinated attack campaigns +- **[Mitigations](./schemas/sdo/mitigation.schema)** - Defensive measures and controls + +### STIX Relationship Objects (SRO) + +- **[Relationships](./schemas/sro/relationship.schema)** - All relationship types and their constraints + +### STIX Meta Objects (SMO) + +- **[Marking Definitions](./schemas/smo/marking-definition.schema)** - Data marking and sharing constraints + +### Additional Object Types + +- **[Data Sources](./schemas/sdo/data-source.schema)** - Detection data categories +- **[Data Components](./schemas/sdo/data-component.schema)** - Specific detection data types +- **[Analytics](./schemas/sdo/analytic.schema)** - Detection analytics and rules +- **[Assets](./schemas/sdo/asset.schema)** - Infrastructure and system assets + +## Configuration Reference + +### Data Source Options + +Complete configuration parameters for all data source types: + +| Source Type | Configuration | Description | +|-------------|---------------|-------------| +| `attack` | `domain`, `version` | Official MITRE ATT&CK repository | +| `file` | `file`, `parsingMode` | Local STIX bundle files | +| `url` | `url`, `parsingMode` | Remote STIX bundle URLs | +| `taxii` | `server`, `collection`, `credentials` | TAXII 2.1 servers *(coming soon)* | + +**[View complete configuration reference โ†’](./configuration)** + +## Error Reference + +### Error Types and Handling + +Comprehensive error codes, meanings, and resolution strategies: + +| Error Type | Description | Common Causes | +|------------|-------------|---------------| +| `ValidationError` | Schema validation failures | Invalid STIX data, missing required fields | +| `DataSourceError` | Data source access issues | Network failures, file not found, authentication | +| `RelationshipError` | Broken object relationships | Missing target objects, invalid references | +| `ParsingError` | Data parsing failures | Malformed JSON, unsupported formats | + +**[View complete error reference โ†’](./errors)** + +## Type Definitions + +### TypeScript Interfaces + +All exported TypeScript types and interfaces: + +```typescript +// Core types +import type { + AttackDataModel, + DataSource, + DataSourceOptions, + ParsingMode +} from '@mitre-attack/attack-data-model'; + +// Schema types +import type { + Technique, + Tactic, + Group, + Campaign, + Malware, + Tool, + Mitigation, + Relationship +} from '@mitre-attack/attack-data-model'; + +// Implementation class types +import type { + TechniqueImpl, + TacticImpl, + GroupImpl, + CampaignImpl, + MalwareImpl, + ToolImpl, + MitigationImpl +} from '@mitre-attack/attack-data-model'; +``` + +## Version Compatibility + +### ATT&CK Specification Versions + +- **Current**: 3.3.0 +- **Supported**: 3.0.0+ +- **Deprecated**: 2.x (legacy support only) + +### Library Versions + +- **Node.js**: 20.0.0+ +- **TypeScript**: 4.5.0+ +- **Zod**: 3.20.0+ + +**[View complete compatibility matrix โ†’](../explanation/compatibility)** + +## Quick Reference + +### Essential Imports + +```typescript +// Main entry points +import { registerDataSource, loadDataModel } from '@mitre-attack/attack-data-model'; + +// Classes +import { DataSource, AttackDataModel } from '@mitre-attack/attack-data-model'; + +// Schemas +import { techniqueSchema, tacticSchema } from '@mitre-attack/attack-data-model'; + +// Types +import type { Technique, Tactic } from '@mitre-attack/attack-data-model'; +``` + +### Common Patterns + +```typescript +// Load ATT&CK data +const dataSource = new DataSource({ source: 'attack', domain: 'enterprise-attack' }); +const uuid = await registerDataSource(dataSource); +const attackDataModel = loadDataModel(uuid); + +// Validate data +const validTechnique = techniqueSchema.parse(techniqueData); + +// Navigate relationships +const tactics = technique.getTactics(); +const subtechniques = technique.getSubtechniques(); +``` + +## Reference Usage + +This reference documentation follows these principles: + +- **Complete**: Every public API method and property is documented +- **Precise**: Exact parameter types, return values, and constraints +- **Systematic**: Consistent organization and formatting +- **Current**: Auto-generated from source code when possible + +**Looking for something specific?** Use the search functionality or check the relevant section above. + +--- diff --git a/docusaurus/docs/reference/schemas/stix-bundle.schema.mdx b/docusaurus/docs/reference/schemas/stix-bundle.schema.mdx new file mode 100644 index 00000000..803fd15d --- /dev/null +++ b/docusaurus/docs/reference/schemas/stix-bundle.schema.mdx @@ -0,0 +1,34 @@ +# STIX Bundle Schema + + + +## StixBundle + +_Object containing the following properties:_ + +| Property | Description | Type | +| :------- |:----------- | :--- | +| **`id`** (\*) | | `any` | +| **`type`** (\*)| | `'bundle'` | +| **`objects`** (\*) | | [AttackObjects](#attackobjects) | + +_(\*) Required._ + +## AttackObjects + +Array of at least 1 +[Asset](sdo/asset.schema) | +[Campaign](sdo/campaign.schema) | +[Collection](sdo/collection.schema) | +[DataComponent](sdo/data-component.schema) | +[DataSource](sdo/data-source.schema) | +[Group](sdo/group.schema) | +[Identity](sdo/identity.schema) | +[Malware](sdo/malware.schema) | +[Matrix](sdo/matrix.schema) | +[Mitigation](sdo/mitigation.schema) | +[Tactic](sdo/tactic.schema) | +[Technique](sdo/technique.schema) | +[Tool](sdo/tool.schema) | +[MarkingDefinition](smo/marking-definition.schema) | +[Relationship](sro/relationship.schema) objects. diff --git a/docusaurus/docs/sdo/stix-bundle.schema.md b/docusaurus/docs/sdo/stix-bundle.schema.md deleted file mode 100644 index e2dc84ba..00000000 --- a/docusaurus/docs/sdo/stix-bundle.schema.md +++ /dev/null @@ -1,18 +0,0 @@ -# STIX Bundle Schema - -## StixBundle - -_Object containing the following properties:_ - -| Property | Description | Type | -| :------- |:----------- | :--- | -| **`id`** (\*) | | `any` | -| **`type`** (\*)| | `'bundle'` | -| **`spec_version`** (\*) | The version of the STIX specification used to represent this object. The value of this property MUST be 2.1 for STIX Objects defined according to this specification. If objects are found where this property is not present, the implicit value for all STIX Objects other than SCOs is 2.0. Since SCOs are now top-level objects in STIX 2.1, the default value for SCOs is 2.1. | `'2.0' \| '2.1'` | -| **`objects`** (\*) | | [AttackObjects](#attackobjects) | - -_(\*) Required._ - -## AttackObjects - -_Array of at least 1 [Asset](/docs/sdo/asset.schema) | [Campaign](/docs/sdo/campaign.schema) | [Collection](/docs/sdo/collection.schema) | [DataComponent](/docs/sdo/data-component.schema) | [DataSource](/docs/sdo/data-source.schema) | [Group](/docs/sdo/group.schema) | [Identity](/docs/sdo/identity.schema) | [Malware](/docs/sdo/malware.schema) | [Matrix](/docs/sdo/matrix.schema) | [Mitigation](/docs/sdo/mitigation.schema) | [Tactic](/docs/sdo/tactic.schema) | [Technique](/docs/sdo/technique.schema) | [Tool](/docs/sdo/tool.schema) | [MarkingDefinition](/docs/smo/marking-definition.schema) | [Relationship](/docs/sro/relationship.schema) objects._ diff --git a/docusaurus/docs/tutorials/index.md b/docusaurus/docs/tutorials/index.md new file mode 100644 index 00000000..75be3b06 --- /dev/null +++ b/docusaurus/docs/tutorials/index.md @@ -0,0 +1,27 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; +import DocCardList from '@theme/DocCardList'; + +# Tutorials + + + +**Learning-oriented guides for getting started with the ATT&CK Data Model** + +Welcome to the tutorials section! These step-by-step guides will teach you how to work with the MITRE ATT&CK Data Model (ADM) from the ground up. Each tutorial is designed to be completed by anyone with basic TypeScript knowledge, regardless of their familiarity with ATT&CK. + +## Prerequisites + +Before starting these tutorials, you should have: + +- **Node.js 16+** installed on your system +- **Basic TypeScript/JavaScript** knowledge (variables, functions, async/await) +- **Terminal/command line** familiarity +- A **text editor or IDE** of your choice + +## Available Tutorials + +These tutorials will take you from zero knowledge to confidently using the ATT&CK Data Model in your applications: + + + +--- diff --git a/docusaurus/docs/tutorials/multi-domain-analysis.md b/docusaurus/docs/tutorials/multi-domain-analysis.md new file mode 100644 index 00000000..31519a77 --- /dev/null +++ b/docusaurus/docs/tutorials/multi-domain-analysis.md @@ -0,0 +1,285 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Multi-Domain ATT&CK Analysis + + + +**Learn to work with Enterprise, Mobile, and ICS ATT&CK domains** + +In this tutorial, you'll learn how to load and analyze data from multiple ATT&CK domains (Enterprise, Mobile, and ICS). You'll compare techniques across domains and understand how adversary tactics vary between different environments. + +## What You'll Build + +You'll create a Node.js script that: + +- Loads data from all three ATT&CK domains +- Compares technique coverage across domains +- Identifies common and domain-specific tactics +- Demonstrates cross-domain analysis patterns + +## Prerequisites + +- Completed the [Your First ATT&CK Query](./your-first-query) tutorial +- Basic understanding of ATT&CK domains (Enterprise, Mobile, ICS) + +## Step 1: Set Up Multi-Domain Project + +Create a new directory for your multi-domain analysis: + +```bash +mkdir multi-domain-analysis +cd multi-domain-analysis +npm init -y +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +## Step 2: Load Multiple ATT&CK Domains + +Create a file named `multi-domain.ts` and add the following code: + +```typescript +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + +async function loadAllDomains() { + console.log('Loading all ATT&CK domains...\n'); + + // Define all three domains + type DomainName = "enterprise-attack" | "mobile-attack" | "ics-attack"; + const domains: { name: DomainName; label: string }[] = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + const dataModels: { [key: string]: any } = {}; + + for (const domain of domains) { + try { + console.log(`Loading ${domain.label} domain...`); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: domain.name, + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + dataModels[domain.name] = loadDataModel(uuid); + const techniqueCount = dataModels[domain.name].techniques.length; + console.log(`${domain.label}: ${techniqueCount} techniques loaded\n`); + } + + } catch (error) { + console.error(`Failed to load ${domain.label} domain:`, error); + } + } + + return dataModels; +} +``` + +## Step 3: Compare Domain Statistics + +Add this function to analyze differences between domains: + +```typescript +function analyzeDomainStatistics(dataModels: { [key: string]: any }) { + console.log('Domain Comparison:\n'); + + const stats: { [key: string]: any } = {}; + + Object.keys(dataModels).forEach(domain => { + const model = dataModels[domain]; + stats[domain] = { + techniques: model.techniques.length, + tactics: model.tactics.length, + groups: model.groups.length, + software: model.malware.length + model.tools.length, + mitigations: model.mitigations.length + }; + }); + + // Display comparison table + console.log('| Metric | Enterprise | Mobile | ICS |'); + console.log('|-------------|------------|--------|-----|'); + + const metrics = ['techniques', 'tactics', 'groups', 'software', 'mitigations']; + + metrics.forEach(metric => { + const enterprise = stats['enterprise-attack']?.[metric] || 0; + const mobile = stats['mobile-attack']?.[metric] || 0; + const ics = stats['ics-attack']?.[metric] || 0; + + console.log(`| ${metric.padEnd(11)} | ${String(enterprise).padEnd(10)} | ${String(mobile).padEnd(6)} | ${String(ics).padEnd(3)} |`); + }); + + console.log('\n'); + return stats; +} +``` + +## Step 4: Find Common Tactics Across Domains + +Add this function to identify tactics that appear in multiple domains: + +```typescript +function findCommonTactics(dataModels: { [key: string]: any }) { + console.log('Tactic Analysis:\n'); + + const tacticsByDomain: { [key: string]: Set } = {}; + + // Collect tactics from each domain + Object.keys(dataModels).forEach(domain => { + tacticsByDomain[domain] = new Set(); + dataModels[domain].tactics.forEach((tactic: any) => { + tacticsByDomain[domain].add(tactic.x_mitre_shortname); + }); + }); + + // Find common tactics + const allTactics = new Set(); + Object.values(tacticsByDomain).forEach(domainTactics => { + domainTactics.forEach(tactic => allTactics.add(tactic)); + }); + + console.log('Common tactics across domains:'); + + allTactics.forEach(tactic => { + const domains = Object.keys(tacticsByDomain).filter(domain => + tacticsByDomain[domain].has(tactic) + ); + + if (domains.length > 1) { + const domainLabels = domains.map(d => { + switch(d) { + case 'enterprise-attack': return 'Enterprise'; + case 'mobile-attack': return 'Mobile'; + case 'ics-attack': return 'ICS'; + default: return d; + } + }).join(', '); + + console.log(`- ${tactic}: ${domainLabels}`); + } + }); + + console.log('\n'); +} +``` + +## Step 5: Analyze Cross-Domain Techniques + +Add this function to find techniques that may be related across domains: + +```typescript +function analyzeCrossDomainTechniques(dataModels: { [key: string]: any }) { + console.log('Cross-Domain Technique Analysis:\n'); + + // Look for techniques with similar names across domains + const enterpriseTechniques = dataModels['enterprise-attack']?.techniques || []; + const mobileTechniques = dataModels['mobile-attack']?.techniques || []; + + console.log('Similar techniques between Enterprise and Mobile:'); + + let similarCount = 0; + + enterpriseTechniques.forEach((entTechnique: any) => { + mobileTechniques.forEach((mobTechnique: any) => { + // Simple name similarity check + const entName = entTechnique.name.toLowerCase(); + const mobName = mobTechnique.name.toLowerCase(); + + if (entName === mobName) { + console.log(`๐Ÿ“ฑ ${entTechnique.name}`); + console.log(` Enterprise: ${entTechnique.external_references[0].external_id}`); + console.log(` Mobile: ${mobTechnique.external_references[0].external_id}\n`); + similarCount++; + } + }); + }); + + console.log(`Found ${similarCount} techniques with identical names across domains.\n`); +} +``` + +## Step 6: Create the Main Analysis Function + +Add the main function to orchestrate the analysis: + +```typescript +async function performMultiDomainAnalysis() { + try { + // Load all domains + const dataModels = await loadAllDomains(); + + if (Object.keys(dataModels).length === 0) { + console.error('No domains loaded successfully'); + return; + } + + // Perform various analyses + analyzeDomainStatistics(dataModels); + findCommonTactics(dataModels); + analyzeCrossDomainTechniques(dataModels); + + console.log('Multi-domain analysis complete!'); + + } catch (error) { + console.error('Analysis failed:', error); + } +} + +// Run the analysis +performMultiDomainAnalysis(); +``` + +## Step 7: Run Your Multi-Domain Analysis + +Execute your script to see the cross-domain analysis: + +```bash +npx tsx multi-domain.ts +``` + +You should see output comparing the domains, identifying common tactics, and highlighting similar techniques. + +## What You've Learned + +In this tutorial, you've learned: + +1. **How to load multiple ATT&CK domains** simultaneously +2. **How to compare domain statistics** like technique and tactic counts +3. **How to identify common patterns** across different attack environments +4. **How to analyze relationships** between Enterprise, Mobile, and ICS domains +5. **How to structure cross-domain analysis** for comprehensive threat intelligence + +## Understanding Multi-Domain ATT&CK + +Key insights from multi-domain analysis: + +- **Enterprise Domain**: Focuses on traditional IT environments with the most comprehensive technique coverage +- **Mobile Domain**: Specialized for mobile device threats with unique tactics like "Initial Access" through app stores +- **ICS Domain**: Covers industrial control systems with tactics specific to operational technology +- **Common Tactics**: Many fundamental tactics like "Execution" and "Persistence" appear across domains +- **Technique Overlap**: Some techniques have similar names but different implementations across domains + +## Next Steps + +Now that you understand cross-domain analysis, explore: + +- **[Understanding Relationships](./relationships)** - Master ATT&CK object connections +- **[How-to: Handle Performance at Scale](../how-to-guides/performance)** - Optimize for large datasets +- **[Explanation: Why ADM Exists](../explanation/why-adm-exists)** - Understand the project's design rationale + +## Common Issues + +**Memory usage**: Loading all domains simultaneously can use significant memory. Consider loading domains individually for analysis if you encounter memory limits. + +**Network timeouts**: Loading multiple domains requires multiple network requests. Ensure stable internet connectivity. + +**Version consistency**: Use the same version number across all domains to ensure compatibility in cross-domain comparisons. + +--- diff --git a/docusaurus/docs/tutorials/relationships.md b/docusaurus/docs/tutorials/relationships.md new file mode 100644 index 00000000..f9981aae --- /dev/null +++ b/docusaurus/docs/tutorials/relationships.md @@ -0,0 +1,476 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Understanding ATT&CK Relationships + + + +**Master navigation between related ATT&CK objects** + +ATT&CK's power comes from the rich relationships between techniques, tactics, groups, software, and mitigations. In this tutorial, you'll learn to navigate these connections to uncover threat intelligence insights and build comprehensive security analysis capabilities. + +## What You'll Learn + +By the end of this tutorial, you'll understand how to: + +- Navigate technique-to-tactic relationships +- Explore group-to-technique connections (procedures) +- Discover software usage patterns +- Find mitigations for specific techniques +- Work with parent/sub-technique hierarchies +- Trace transitive relationships across object types + +## Prerequisites + +Complete the previous tutorials: + +- **[Your First ATT&CK Query](./your-first-query)** +- **[Building a Technique Browser](./technique-browser)** + +## Step 1: Set Up Your Relationship Explorer + +Create a new project: + +```bash +mkdir attack-relationships +cd attack-relationships +npm init -y +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +Create `relationship-explorer.ts`: + +```typescript +import { registerDataSource, loadDataModel, DataSourceRegistration, AttackDataModel } from '@mitre-attack/attack-data-model'; + +class RelationshipExplorer { + private attackDataModel: AttackDataModel; + + async initialize(): Promise { + console.log('Initializing ATT&CK Relationship Explorer...\n'); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (!uuid) throw new Error('Failed to register data source'); + + this.attackDataModel = loadDataModel(uuid); + console.log('Data loaded successfully!\n'); + } + + // Returns an array of tactic objects for a given technique + getTechniqueTactics(technique: any): any[] { + const killChainPhases = technique.kill_chain_phases || []; + const tacticShortnames = killChainPhases.map(phase => phase.phase_name); + + const tactics = this.attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + return tactics; + } + + // We'll add exploration methods here... +} + +// Initialize and run examples +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // We'll add example calls here... +} + +main().catch(console.error); +``` + +## Step 2: Explore Technique-Tactic Relationships + +Add this method to understand how techniques map to tactical goals: + +```typescript + exploreTechniqueTactics(): void { + console.log('TECHNIQUE-TACTIC RELATIONSHIPS\n'); + + // Find a specific technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' + ) as TechniqueImpl | undefined; + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 100)}...\n`); + + // Get associated tactics + const tactics = this.getTechniqueTactics(technique); + console.log(`This technique is used for ${tactics.length} tactical goal(s):`); + + tactics.forEach((tactic, index) => { + console.log(`${index + 1}. ${tactic.name}`); + console.log(` Purpose: ${tactic.description.substring(0, 80)}...\n`); + }); + + console.log('This shows how one technique can serve multiple adversary goals!\n'); + } +``` + +Add the method call to your main function: + +```typescript + // In main function: + await explorer.exploreTechniqueTactics(); +``` + +## Step 3: Discover Group-Technique Relationships (Procedures) + +Add this method to explore how groups use techniques: + +```typescript + exploreGroupTechniques(): void { + console.log('GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); + + // Find APT1 group + const apt1 = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' + ); + + if (!apt1) return; + + console.log(`Group: ${apt1.name} (${apt1.external_references[0].external_id})`); + console.log(`Description: ${apt1.description.substring(0, 120)}...\n`); + + // Get techniques used by this group + const techniques = apt1.getTechniques(); + console.log(`This group uses ${techniques.length} different techniques:`); + + // Show first 5 techniques + techniques.slice(0, 5).forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Find the relationship to get procedure details + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === apt1.id && + rel.target_ref === technique.id && + rel.relationship_type === 'uses' + ); + + if (relationship && relationship.description) { + console.log(` Procedure: ${relationship.description.substring(0, 100)}...`); + } + console.log(''); + }); + + console.log(`... and ${techniques.length - 5} more techniques\n`); + } +``` + +## Step 4: Explore Software Usage Patterns + +Add this method to understand software-technique relationships: + +```typescript + exploreSoftwareUsage(): void { + console.log('SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); + + // Find Mimikatz (a well-known tool) + const mimikatz = this.attackDataModel.tools.find(tool => + tool.name.toLowerCase().includes('mimikatz') + ); + + if (!mimikatz) return; + + console.log(`Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); + console.log(`Description: ${mimikatz.description.substring(0, 120)}...\n`); + + // Get techniques used by this software + const techniques = mimikatz.getTechniques(); + console.log(`This tool implements ${techniques.length} techniques:`); + + techniques.forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + + console.log(` Supports tactics: ${tactics.map(t => t.name).join(', ')}\n`); + }); + const relationships = this.attackDataModel.relationships; + + const mimikatzId = mimikatz.id; + + // Find all "uses" relationships where target is Mimikatz + const groupUsesMimikatz = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.target_ref === mimikatzId && + rel.source_ref.startsWith("intrusion-set--") // group id prefix + ); + + // Get group objects + const groupsUsingMimikatz = groupUsesMimikatz.map(rel => + this.attackDataModel.groups.find(group => group.id === rel.source_ref) + ).filter(Boolean); // Remove undefined if any + + console.log(`This tool is used by ${groupsUsingMimikatz.length} groups:`); + groupsUsingMimikatz.slice(0, 3).forEach((group, index) => { + console.log(`${index + 1}. ${group.name} (${group.external_references[0].external_id})`); + }); + console.log(''); + } +``` + +## Step 5: Navigate Parent-Child Technique Relationships + +Add this method to explore sub-technique hierarchies: + +```typescript + exploreSubtechniqueRelationships(): void { + console.log('PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); + + // Find a parent technique with sub-techniques + const parentTechnique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1003' && + !t.x_mitre_is_subtechnique + ) as TechniqueImpl | undefined; + + if (!parentTechnique) return; + + console.log(`Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); + console.log(`Description: ${parentTechnique.description.substring(0, 120)}...\n`); + + // Get sub-techniques + + const subTechniques = parentTechnique.getSubTechniques(); + console.log(`This technique has ${subTechniques.length} sub-techniques:`); + + subTechniques.forEach((subTech, index) => { + console.log(`${index + 1}. ${subTech.name} (${subTech.external_references[0].external_id})`); + console.log(` Platforms: ${subTech.x_mitre_platforms?.join(', ') || 'Not specified'}\n`); + }); + + // Navigate back from sub-technique to parent + if (subTechniques.length > 0) { + const firstSubTech = subTechniques[0]; + const parentFromChild = firstSubTech.getParentTechnique(); + + console.log(`Navigation verification:`); + console.log(`Sub-technique "${firstSubTech.name}" โ†’ Parent: "${parentFromChild?.name}"`); + console.log(`Bidirectional navigation works!\n`); + } + } +``` + +## Step 6: Discover Mitigation Relationships + +Add this method to find defensive measures: + +```typescript + exploreMitigationRelationships(): void { + console.log('MITIGATION-TECHNIQUE RELATIONSHIPS\n'); + + // Find a technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1059' + ); + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 120)}...\n`); + + // Get mitigations for this technique + const mitigations = technique.getMitigations(); + console.log(`This technique can be mitigated by ${mitigations.length} measures:`); + + mitigations.forEach((mitigation, index) => { + console.log(`${index + 1}. ${mitigation.name} (${mitigation.external_references[0].external_id})`); + + // Find the relationship to get mitigation guidance + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === mitigation.id && + rel.target_ref === technique.id && + rel.relationship_type === 'mitigates' + ); + + if (relationship && relationship.description) { + console.log(` Guidance: ${relationship.description.substring(0, 100)}...\n`); + } + }); + } +``` + +## Step 7: Explore Transitive Relationships + +Add this method to trace complex relationship chains: + +```typescript + exploreTransitiveRelationships(): void { + console.log('TRANSITIVE RELATIONSHIPS (Group โ†’ Software โ†’ Techniques)\n'); + + // Find a group + const group = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0016' + ); + + if (!group) return; + + console.log(`Group: ${group.name} (${group.external_references[0].external_id})`); + console.log(`Description: ${group.description.substring(0, 120)}...\n`); + + const relationships = this.attackDataModel.relationships; + + // Get software used by the group + const softwareUsedByGroup = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.source_ref === group.id && + ( + rel.target_ref.startsWith("malware--") || + rel.target_ref.startsWith("tool--") + ) + ); + + // Get software objects + const allSoftware = [ + ...this.attackDataModel.malware, + ...this.attackDataModel.tools + ]; + const software = softwareUsedByGroup.map(rel => + allSoftware.find(soft => soft.id === rel.target_ref) + ).filter(Boolean); + + console.log(`This group uses ${software.length} software tools:`); + + software.slice(0, 3).forEach((soft, index) => { + console.log(`\n${index + 1}. ${soft.name} (${soft.external_references[0].external_id})`); + + // Get techniques used by this software + const techniques = soft.getTechniques(); + console.log(` โ†’ Implements ${techniques.length} techniques:`); + + techniques.slice(0, 2).forEach((technique, techIndex) => { + console.log(` ${techIndex + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show tactics supported + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + console.log(` Tactics: ${tactics.map(t => t.name).join(', ')}`); + }); + + if (techniques.length > 2) { + console.log(` ... and ${techniques.length - 2} more techniques`); + } + }); + + console.log(`\nThis shows the relationship chain: Group โ†’ Software โ†’ Techniques โ†’ Tactics\n`); + } +``` + +## Step 8: Run All Relationship Explorations + +Update your main function to run all examples: + +```typescript +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // Run all relationship explorations + explorer.exploreTechniqueTactics(); + explorer.exploreGroupTechniques(); + explorer.exploreSoftwareUsage(); + explorer.exploreSubtechniqueRelationships(); + explorer.exploreMitigationRelationships(); + explorer.exploreTransitiveRelationships(); + + console.log('Relationship exploration complete!\n'); + console.log('Key takeaways:'); + console.log(' - ATT&CK objects are richly interconnected'); + console.log(' - Relationships carry descriptive context'); + console.log(' - Navigation methods simplify complex queries'); + console.log(' - Transitive relationships reveal attack patterns'); +} +``` + +## Step 9: Run Your Relationship Explorer + +Execute your script to see ATT&CK relationships in action: + +```bash +npx tsx relationship-explorer.ts +``` + +You'll see a comprehensive exploration of ATT&CK relationships, showing how different objects connect and relate to each other. + +## What You've Learned + +In this tutorial, you've mastered: + +1. **Technique-Tactic Mapping**: Understanding how techniques serve tactical goals +2. **Procedure Discovery**: Finding how groups use specific techniques +3. **Software Analysis**: Exploring tool capabilities and usage +4. **Hierarchy Navigation**: Working with parent and sub-technique relationships +5. **Mitigation Research**: Discovering defensive measures for techniques +6. **Transitive Relationships**: Tracing complex relationship chains + +## Key Relationship Navigation Methods + +You've used these essential methods: + +- **`getTactics()`**: Find tactics associated with a technique +- **`getTechniques()`**: Get techniques used by groups or software +- **`getSubtechniques()`** / **`getParentTechnique()`**: Navigate technique hierarchies +- **`getMitigations()`**: Find defensive measures for techniques +- **`getAssociatedSoftware()`**: Discover tools used by groups + +## Real-World Applications + +These relationship navigation skills enable: + +- **Threat Intelligence**: Mapping adversary capabilities and behaviors +- **Security Analysis**: Understanding attack patterns and defense priorities +- **Tool Development**: Building comprehensive security platforms +- **Research**: Discovering trends and patterns in adversary tactics + +## Advanced Relationship Patterns + +Try exploring these patterns on your own: + +```typescript +// Find all groups that use a specific technique +const groupsUsingTechnique = attackDataModel.groups.filter(group => + group.getTechniques().some(tech => tech.id === specificTechnique.id) +); + +// Find techniques that have no mitigations +const unmitigatedTechniques = attackDataModel.techniques.filter(tech => + tech.getMitigations().length === 0 +); + +// Find software used across multiple tactics +const multiTacticSoftware = attackDataModel.tools.filter(tool => + new Set(tool.getTechniques().flatMap(tech => this.getTechniqueTactics(tech))).size > 3 +); +``` + +## Next Steps + +You've completed all tutorials! Now you can: + +- **[Apply these skills in How-to Guides](../how-to-guides/)** - Solve specific problems +- **[Explore the Reference Documentation](../reference/)** - Understand the complete API +- **[Read Explanations](../explanation/)** - Learn about design decisions and architecture +- **Build your own applications** using the patterns you've learned + +## Troubleshooting + +**"Cannot read property of undefined" errors**: Some objects might not have all expected relationships. Always check if methods return empty arrays or null values. + +**Performance with large datasets**: Relationship navigation is efficient, but filtering large result sets may take time. Consider caching results for repeated queries. + +**Missing relationships**: Remember that ATT&CK data evolves. Some relationships may not exist in all versions or domains. + +--- diff --git a/docusaurus/docs/tutorials/technique-browser.md b/docusaurus/docs/tutorials/technique-browser.md new file mode 100644 index 00000000..3f9de828 --- /dev/null +++ b/docusaurus/docs/tutorials/technique-browser.md @@ -0,0 +1,450 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Building a Technique Browser + + + +**Create a complete application that browses ATT&CK techniques** + +In this tutorial, you'll build a complete command-line application that lets you browse and search ATT&CK techniques interactively. You'll learn to work with multiple object types, implement search functionality, and create a practical tool you can use and extend. + +## What You'll Build + +A command-line technique browser with these features: + +- Interactive menu system +- Search techniques by name or ATT&CK ID +- Browse techniques by tactic +- View technique details with relationships +- Navigate between parent techniques and sub-techniques + +## Prerequisites + +Complete the **[Your First ATT&CK Query](./your-first-query)** tutorial first to understand the basics. + +## Step 1: Set Up the Project + +Create a new project directory: + +```bash +mkdir attack-technique-browser +cd attack-technique-browser +npm init -y +``` + +Install dependencies including a library for interactive prompts: + +```bash +npm install @mitre-attack/attack-data-model prompts +npm install -D typescript tsx @types/node @types/prompts +``` + +## Step 2: Create the Core Browser Class + +Create `technique-browser.ts` with the foundation of our browser: + +```typescript +import { registerDataSource, loadDataModel, DataSource, AttackDataModel, TechniqueImpl } from '@mitre-attack/attack-data-model'; +import prompts from 'prompts'; + +class TechniqueBrowser { + private attackDataModel?: AttackDataModel; + private isInitialized = false; + + async initialize(): Promise { + console.log('๐ŸŽฏ Initializing ATT&CK Technique Browser...\n'); + + const dataSource = new DataSource({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed' + }); + + try { + const uuid = await registerDataSource(dataSource); + + if (uuid) { + this.attackDataModel = loadDataModel(uuid); + this.isInitialized = true; + + console.log(`โœ… Loaded ${this.attackDataModel.techniques.length} techniques`); + console.log(`โœ… Loaded ${this.attackDataModel.tactics.length} tactics\n`); + } + } catch (error) { + console.error('โŒ Failed to initialize:', error); + throw error; + } + } + + private ensureInitialized(): AttackDataModel { + if (!this.isInitialized || !this.attackDataModel) { + throw new Error('Browser not initialized. Call initialize() first.'); + } + return this.attackDataModel; + } + + // We'll add more methods here... +} +``` + +## Step 3: Add the Main Menu + +Add the main menu system to the `TechniqueBrowser` class: + +```typescript + async run(): Promise { + this.ensureInitialized(); + + let running = true; + + while (running) { + console.log('\n' + '='.repeat(50)); + console.log('๐ŸŽฏ ATT&CK Technique Browser'); + console.log('='.repeat(50)); + + const response = await prompts({ + type: 'select', + name: 'action', + message: 'What would you like to do?', + choices: [ + { title: '๐Ÿ” Search techniques by name', value: 'search' }, + { title: '๐Ÿท๏ธ Find technique by ATT&CK ID', value: 'findById' }, + { title: '๐ŸŽฏ Browse techniques by tactic', value: 'browseByTactic' }, + { title: '๐Ÿ“Š Show statistics', value: 'stats' }, + { title: '๐Ÿ‘‹ Exit', value: 'exit' } + ] + }); + + switch (response.action) { + case 'search': + await this.searchTechniques(); + break; + case 'findById': + await this.findTechniqueById(); + break; + case 'browseByTactic': + await this.browseByTactic(); + break; + case 'stats': + await this.showStatistics(); + break; + case 'exit': + running = false; + console.log('\n๐Ÿ‘‹ Thanks for using ATT&CK Technique Browser!'); + break; + default: + console.log('Invalid selection. Please try again.'); + } + } + } +``` + +## Step 4: Implement Search Functionality + +Add these methods to implement search features: + +```typescript + private async searchTechniques(): Promise { + const attackDataModel = this.ensureInitialized(); + + const response = await prompts({ + type: 'text', + name: 'searchTerm', + message: 'Enter search term (technique name):', + validate: value => value.length < 1 ? 'Search term cannot be empty' : true + }); + + if (!response.searchTerm) return; + + const searchTerm = response.searchTerm.toLowerCase(); + const matches = attackDataModel.techniques.filter(technique => + technique.name.toLowerCase().includes(searchTerm) + ); + + if (matches.length === 0) { + console.log(`\nโŒ No techniques found containing "${response.searchTerm}"`); + return; + } + + console.log(`\n๐Ÿ” Found ${matches.length} technique(s):`); + matches.slice(0, 10).forEach((technique, index) => { + const attackId = technique.external_references[0].external_id; + const isSubTech = technique.x_mitre_is_subtechnique ? ' (Sub-technique)' : ''; + console.log(`${index + 1}. ${technique.name} (${attackId})${isSubTech}`); + }); + + if (matches.length > 10) { + console.log(`... and ${matches.length - 10} more`); + } + + await this.selectAndViewTechnique(matches.slice(0, 10)); + } + + private async findTechniqueById(): Promise { + const attackDataModel = this.ensureInitialized(); + + const response = await prompts({ + type: 'text', + name: 'attackId', + message: 'Enter ATT&CK ID (e.g., T1003, T1055.001):', + validate: value => value.length < 1 ? 'ATT&CK ID cannot be empty' : true + }); + + if (!response.attackId) return; + + const technique = attackDataModel.techniques.find(tech => + tech.external_references[0].external_id.toLowerCase() === response.attackId.toLowerCase() + ); + + if (!technique) { + console.log(`\nโŒ No technique found with ID "${response.attackId}"`); + return; + } + + await this.viewTechniqueDetails(technique); + } +``` + +## Step 5: Add Technique Viewing + +Add methods to display technique details: + +```typescript + private async selectAndViewTechnique(techniques: TechniqueImpl[]): Promise { + if (techniques.length === 0) return; + + const choices = techniques.map((technique, index) => ({ + title: `${technique.name} (${technique.external_references[0].external_id})`, + value: index + })); + + choices.push({ title: 'โ† Back to main menu', value: -1 }); + + const response = await prompts({ + type: 'select', + name: 'selection', + message: 'Select a technique to view details:', + choices + }); + + if (response.selection >= 0) { + await this.viewTechniqueDetails(techniques[response.selection]); + } + } + + private async viewTechniqueDetails(technique: TechniqueImpl): Promise { + console.log('\n' + '='.repeat(60)); + console.log(`๐Ÿ“‹ ${technique.name}`); + console.log('='.repeat(60)); + + const attackId = technique.external_references[0].external_id; + console.log(`ATT&CK ID: ${attackId}`); + + if (technique.x_mitre_is_subtechnique) { + console.log('Type: Sub-technique'); + const parent = technique.getParentTechnique(); + if (parent) { + console.log(`Parent: ${parent.name} (${parent.external_references[0].external_id})`); + } + } else { + console.log('Type: Parent technique'); + const subtechniques = technique.getSubtechniques(); + if (subtechniques.length > 0) { + console.log(`Sub-techniques: ${subtechniques.length}`); + } + } + + console.log(`\nPlatforms: ${technique.x_mitre_platforms?.join(', ') || 'Not specified'}`); + + const tactics = technique.getTactics(); + console.log(`Tactics: ${tactics.map(t => t.name).join(', ')}`); + + console.log(`\nDescription:`); + console.log(technique.description); + + await prompts({ + type: 'confirm', + name: 'continue', + message: 'Press Enter to continue...', + initial: true + }); + } +``` + +## Step 6: Add Browse by Tactic Feature + +Add the ability to browse techniques organized by tactic: + +```typescript + private async browseByTactic(): Promise { + const attackDataModel = this.ensureInitialized(); + + const tactics = attackDataModel.tactics.sort((a, b) => a.name.localeCompare(b.name)); + + const choices = tactics.map(tactic => ({ + title: `${tactic.name} - ${tactic.description.substring(0, 60)}...`, + value: tactic + })); + + choices.push({ title: 'โ† Back to main menu', value: null }); + + const response = await prompts({ + type: 'select', + name: 'tactic', + message: 'Select a tactic to browse techniques:', + choices + }); + + if (!response.tactic) return; + + const tacticTechniques = attackDataModel.techniques.filter(technique => + technique.getTactics().some(t => t.id === response.tactic.id) + ); + + console.log(`\n๐ŸŽฏ Techniques for "${response.tactic.name}" (${tacticTechniques.length} total):`); + + await this.selectAndViewTechnique(tacticTechniques); + } + + private async showStatistics(): Promise { + const attackDataModel = this.ensureInitialized(); + + const totalTechniques = attackDataModel.techniques.length; + const parentTechniques = attackDataModel.techniques.filter(t => !t.x_mitre_is_subtechnique).length; + const subTechniques = attackDataModel.techniques.filter(t => t.x_mitre_is_subtechnique).length; + + console.log('\n๐Ÿ“Š ATT&CK Statistics:'); + console.log(`Total Techniques: ${totalTechniques}`); + console.log(`Parent Techniques: ${parentTechniques}`); + console.log(`Sub-techniques: ${subTechniques}`); + console.log(`Total Tactics: ${attackDataModel.tactics.length}`); + console.log(`Total Groups: ${attackDataModel.groups.length}`); + console.log(`Total Software: ${attackDataModel.malware.length + attackDataModel.tools.length}`); + + await prompts({ + type: 'confirm', + name: 'continue', + message: 'Press Enter to continue...', + initial: true + }); + } +``` + +## Step 7: Create the Main Application File + +Create `app.ts` to tie everything together: + +```typescript +import { TechniqueBrowser } from './technique-browser.js'; + +async function main() { + const browser = new TechniqueBrowser(); + + try { + await browser.initialize(); + await browser.run(); + } catch (error) { + console.error('โŒ Application error:', error); + process.exit(1); + } +} + +main().catch(console.error); +``` + +## Step 8: Add TypeScript Configuration + +Create `tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist" + }, + "include": ["*.ts"], + "exclude": ["node_modules"] +} +``` + +## Step 9: Add Package Scripts + +Update your `package.json` to include helpful scripts: + +```json +{ + "scripts": { + "start": "npx tsx app.ts", + "dev": "npx tsx --watch app.ts", + "build": "npx tsc", + "run": "node dist/app.js" + }, + "type": "module" +} +``` + +## Step 10: Run Your Application + +Start your technique browser: + +```bash +npm start +``` + +You should see an interactive menu that lets you explore ATT&CK techniques in multiple ways! + +## Testing Your Browser + +Try these actions to explore your application: + +1. **Search for "credential"** - find techniques related to credential access +2. **Look up technique "T1003"** - view details about OS Credential Dumping +3. **Browse the "Initial Access" tactic** - see how adversaries get initial footholds +4. **Check statistics** - understand the scope of ATT&CK data + +## What You've Learned + +In this tutorial, you've learned how to: + +1. **Structure a complete application** using the ATT&CK Data Model +2. **Implement search functionality** across technique names and IDs +3. **Navigate relationships** between techniques, tactics, and sub-techniques +4. **Create interactive menus** for user-friendly data exploration +5. **Organize techniques by tactics** to understand adversary goals +6. **Display comprehensive technique details** including relationships + +## Key Concepts Mastered + +- **TechniqueImpl methods**: `getTactics()`, `getParentTechnique()`, `getSubtechniques()` +- **Relationship navigation**: Moving between related ATT&CK objects +- **Data filtering and searching**: Finding specific techniques in large datasets +- **Application architecture**: Organizing code into reusable classes +- **User interaction**: Creating friendly command-line interfaces + +## Extending Your Browser + +Consider adding these features: + +- Export search results to JSON or CSV +- Add filtering by platforms (Windows, Linux, macOS) +- Include software and groups that use techniques +- Add bookmarking for frequently viewed techniques +- Implement technique comparison features + +## Next Steps + +You're now ready for more advanced topics: + +- **[Understanding ATT&CK Relationships](./relationships)** - Master complex relationship navigation +- **[How-to Guides](../how-to-guides/)** - Solve specific problems like validation and schema extension +- **[Reference Documentation](../reference/)** - Explore the complete API + +--- diff --git a/docusaurus/docs/tutorials/your-first-query.md b/docusaurus/docs/tutorials/your-first-query.md new file mode 100644 index 00000000..b4778d63 --- /dev/null +++ b/docusaurus/docs/tutorials/your-first-query.md @@ -0,0 +1,218 @@ +import WorkInProgressNotice from '@site/src/components/WorkInProgressNotice'; + +# Your First ATT&CK Query + + + +In this tutorial, you'll learn how to install the ATT&CK Data Model library, load your first ATT&CK dataset, and explore techniques and tactics. By the end, you'll have a working script that can query ATT&CK data and understand the basic concepts of the ATT&CK framework. + +## What You'll Build + +You'll create a simple Node.js script that: + +- Loads the latest ATT&CK Enterprise data +- Lists the first 5 techniques +- Shows the tactics associated with a technique +- Demonstrates basic relationship navigation + +## Step 1: Set Up Your Project + +First, create a new directory for your project and initialize it: + +```bash +mkdir my-first-attack-query +cd my-first-attack-query +npm init -y +``` + +You should see output like: + +```shell +Wrote to /path/to/my-first-attack-query/package.json +``` + +## Step 2: Install the ATT&CK Data Model + +Install the ATT&CK Data Model library and TypeScript tools: + +```bash +npm install @mitre-attack/attack-data-model +npm install -D typescript tsx @types/node +``` + +You should see the packages being installed. The installation may take a minute as it downloads all dependencies. + +## Step 3: Create Your First Script + +Create a file named `first-query.ts` and add the following code: + +```typescript +import { + registerDataSource, + loadDataModel, + DataSourceRegistration, +} from '@mitre-attack/attack-data-model'; + +async function exploreAttackData() { + console.log('Loading ATT&CK Enterprise data...\n'); + + // Step 1: Create a data source + const dataSource = new DataSourceRegistration({ + source: 'attack', // Load from official ATT&CK repository + domain: 'enterprise-attack', // Focus on Enterprise domain + version: '17.1', // Use specific version for consistency + parsingMode: 'relaxed', // Continue even if some data has minor issues + }); + + try { + // Step 2: Register the data source + const uuid = await registerDataSource(dataSource); + + if (uuid) { + console.log('Data source registered successfully!\n'); + + // Step 3: Load the data model + const attackDataModel = loadDataModel(uuid); + console.log(`Loaded ${attackDataModel.techniques.length} techniques\n`); + + // Step 4: Explore the first few techniques + console.log('First 5 techniques:'); + attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { + console.log( + `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`, + ); + }); + } else { + console.error('Failed to register data source'); + } + } catch (error) { + console.error('Error:', error); + } +} + +// Run the function +exploreAttackData(); +``` + +## Step 4: Run Your Script + +Execute your script to see the ATT&CK data in action: + +```bash +npx tsx first-query.ts +``` + +You should see output similar to: + +```shell +Loading ATT&CK Enterprise data... + +Data source registered successfully! + +Loaded 196+ techniques + +First 5 techniques: +1. OS Credential Dumping (T1003) +2. Boot or Logon Autostart Execution (T1547) +3. Process Injection (T1055) +4. Command and Scripting Interpreter (T1059) +5. Ingress Tool Transfer (T1105) +``` + +**Congratulations!** You've just loaded and explored your first ATT&CK dataset! + +## Step 5: Explore Technique Details + +Now let's look at more details about a specific technique. Add this code to your script after the previous console.log statements: + +```typescript + console.log('\nExamining a specific technique:'); + const firstTechnique = attackDataModel.techniques[0]; + + console.log(`Name: ${firstTechnique.name}`); + console.log(`ID: ${firstTechnique.external_references[0].external_id}`); + console.log(`Description: ${firstTechnique.description.substring(0, 100)}...`); + + // Check if it's a subtechnique + if (firstTechnique.x_mitre_is_subtechnique) { + console.log('This is a sub-technique'); + } else { + console.log('This is a parent technique'); + } +``` + +## Step 6: Discover Associated Tactics + +ATT&CK techniques are organized under tactics (the "why" behind adversary actions). Let's explore this relationship: + +```typescript + console.log('\nAssociated tactics:'); + const killChainPhases = firstTechnique.kill_chain_phases || []; + const tacticShortnames = killChainPhases + .map(phase => phase.phase_name); + + const associatedTactics = attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + + if (associatedTactics.length > 0) { + associatedTactics.forEach(tactic => { + console.log(`- ${tactic.name}: ${tactic.description.replace(/\n/g, ' ').substring(0, 60)}...`); + }); + } else { + console.log('No tactics associated with this technique.'); + } +``` + +## Step 7: Run Your Enhanced Script + +Run your enhanced script to see the additional information: + +```bash +npx tsx first-query.ts +``` + +Your output should now include technique details and associated tactics, showing you the rich relationships within ATT&CK data. + +## What You've Learned + +In this tutorial, you've learned: + +1. **How to install** the ATT&CK Data Model library +2. **How to create a DataSource** pointing to official ATT&CK data +3. **How to register and load** ATT&CK datasets +4. **How to access technique** properties like name, ID, and description +5. **How to navigate relationships** between techniques and tactics +6. **The difference** between parent techniques and sub-techniques + +## Understanding the Code + +Let's break down the key concepts you used: + +- **DataSource**: Tells the library where to find ATT&CK data (official repository, local file, URL, etc.) +- **registerDataSource**: Validates and caches the data source, returning a unique identifier +- **loadDataModel**: Retrieves the cached AttackDataModel instance using the identifier +- **AttackDataModel**: The main class containing all ATT&CK objects with automatic relationship mapping +- **getTactics()**: A relationship navigation method that returns related tactics + +## Next Steps + +Now that you understand the basics, you're ready for the next tutorial: + +- **[Building a Technique Browser](./technique-browser)** - Create a complete application that browses techniques + +Or explore other documentation: + +- **[How-to Guides](../how-to-guides/)** - Solve specific problems +- **[Reference](../reference/)** - Detailed API documentation +- **[Examples](https://github.com/mitre-attack/attack-data-model/tree/main/examples)** - More code samples + +## Common Issues + +**"Module not found" errors**: Make sure you've installed all dependencies with `npm install` + +**Network errors during data loading**: The library downloads ATT&CK data from GitHub. Ensure you have internet connectivity. + +**TypeScript compilation errors**: Make sure you're using `npx tsx` to run TypeScript directly, or compile with `npx tsc` first. + +--- diff --git a/docusaurus/docusaurus.config.ts b/docusaurus/docusaurus.config.ts index 9461f7ed..537e1138 100644 --- a/docusaurus/docusaurus.config.ts +++ b/docusaurus/docusaurus.config.ts @@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic'; const config: Config = { title: 'MITRE ATT&CK Data Model', - tagline: '', + tagline: 'A TypeScript library for working with MITRE ATT&CKยฎ data using STIX 2.1', favicon: 'img/favicon.ico', // Set the production url of your site here @@ -35,13 +35,10 @@ const config: Config = { { docs: { sidebarPath: './sidebars.ts', + editUrl: 'https://github.com/mitre-attack/attack-data-model/tree/main/docusaurus/', + showLastUpdateAuthor: true, + showLastUpdateTime: true, }, - // blog: { - // showReadingTime: false, - // path: 'blog', - // routeBasePath: 'known-issues', - // blogTitle: 'Known Issues', - // }, theme: { customCss: './src/css/custom.css', }, @@ -50,6 +47,17 @@ const config: Config = { ], themeConfig: { + // Enhanced metadata for better SEO and social sharing + metadata: [ + { + name: 'keywords', + content: 'MITRE, ATT&CK, STIX, TypeScript, threat intelligence, cybersecurity, data model', + }, + { + name: 'description', + content: 'A TypeScript library for working with MITRE ATT&CKยฎ data using STIX 2.1. Provides type-safe access to ATT&CK objects.', + }, + ], navbar: { title: 'ATT&CK Data Model', logo: { @@ -59,15 +67,10 @@ const config: Config = { items: [ { type: 'docSidebar', - sidebarId: 'tutorialSidebar', + sidebarId: 'documentationSidebar', position: 'left', - label: 'ATT&CK Schemas', + label: 'Documentation', }, - // { - // to: '/known-issues', - // label: 'Known Issues', - // position: 'left', - // }, { href: 'https://github.com/mitre-attack/attack-data-model', label: 'GitHub', @@ -75,27 +78,74 @@ const config: Config = { }, ], }, + // Enable local search for better user experience + // Note: For production, consider configuring Algolia search + docs: { + sidebar: { + hideable: true, + autoCollapseCategories: true, + }, + }, footer: { links: [ { - label: 'ATT&CK Website', - href: 'https://attack.mitre.org', - }, - { - label: 'Contact Us', - href: 'https://attack.mitre.org/resources/engage-with-attack/contact', + title: 'Documentation', + items: [ + { + label: 'Tutorials', + to: '/docs/tutorials/', + }, + { + label: 'How-to Guides', + to: '/docs/how-to-guides/', + }, + { + label: 'Reference', + to: '/docs/reference/', + }, + { + label: 'Principles', + to: '/docs/principles/', + }, + ], }, { - label: 'Terms of Use', - href: 'https://attack.mitre.org/resources/legal-and-branding/terms-of-use', + title: 'Community', + items: [ + { + label: 'GitHub', + href: 'https://github.com/mitre-attack/attack-data-model', + }, + { + label: 'Issues', + href: 'https://github.com/mitre-attack/attack-data-model/issues', + }, + ], }, { - label: 'Privacy Policy', - href: 'https://attack.mitre.org/resources/legal-and-branding/privacy', + title: 'MITRE ATT&CK', + items: [ + { + label: 'ATT&CK Website', + href: 'https://attack.mitre.org', + }, + { + label: 'Contact Us', + href: 'https://attack.mitre.org/resources/engage-with-attack/contact', + }, + { + label: 'Terms of Use', + href: 'https://attack.mitre.org/resources/legal-and-branding/terms-of-use', + }, + { + label: 'Privacy Policy', + href: 'https://attack.mitre.org/resources/legal-and-branding/privacy', + }, + ], }, ], style: 'dark', - copyright: `ยฉ ${new Date().getFullYear()}, The MITRE Corporation. Built with Docusaurus.`, + copyright: `ยฉ ${new Date().getFullYear()}, The MITRE Corporation. Built with Docusaurus.`, }, prism: { theme: prismThemes.github, diff --git a/docusaurus/package-lock.json b/docusaurus/package-lock.json index 47c6c92a..132db694 100644 --- a/docusaurus/package-lock.json +++ b/docusaurus/package-lock.json @@ -20,6 +20,8 @@ "@docusaurus/module-type-aliases": "3.8.1", "@docusaurus/tsconfig": "3.8.1", "@docusaurus/types": "3.8.1", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "typescript": "~5.8.3" }, "engines": { @@ -3826,6 +3828,197 @@ "node": ">=18.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3841,6 +4034,124 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -4014,12 +4325,334 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/config": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.3.4.tgz", + "integrity": "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/config/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@npmcli/config/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 8" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, "node_modules/@pnpm/config.env-replace": { @@ -4416,6 +5049,16 @@ "@types/node": "*" } }, + "node_modules/@types/concat-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-2.0.3.tgz", + "integrity": "sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -4543,6 +5186,13 @@ "@types/node": "*" } }, + "node_modules/@types/is-empty": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/is-empty/-/is-empty-1.2.3.tgz", + "integrity": "sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4738,6 +5388,13 @@ "@types/node": "*" } }, + "node_modules/@types/supports-color": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -4932,6 +5589,16 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -4976,9 +5643,9 @@ } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6111,6 +6778,22 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -6350,9 +7033,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6880,6 +7563,14 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -7017,6 +7708,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7242,6 +7943,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -7303,54 +8011,372 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-mdx/-/eslint-mdx-3.6.2.tgz", + "integrity": "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "espree": "^9.6.1 || ^10.4.0", + "estree-util-visit": "^2.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "unified-engine": "^11.2.2", + "unist-util-visit": "^5.0.0", + "uvu": "^0.5.6", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0", + "remark-lint-file-extension": "*" + }, + "peerDependenciesMeta": { + "remark-lint-file-extension": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mdx/-/eslint-plugin-mdx-3.6.2.tgz", + "integrity": "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-mdx": "^3.6.2", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { + "node_modules/eslint/node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -7358,17 +8384,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7384,6 +8415,31 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -7731,6 +8787,14 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -7817,6 +8881,20 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -7972,6 +9050,29 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -7992,6 +9093,36 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data-encoder": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", @@ -8616,6 +9747,26 @@ "react-is": "^16.7.0" } }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -8995,6 +10146,17 @@ "node": ">=8" } }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9166,6 +10328,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -9363,6 +10532,22 @@ "node": ">=0.10.0" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -9480,6 +10665,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9565,6 +10758,21 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -9583,6 +10791,21 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/load-plugin": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.3.tgz", + "integrity": "sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@npmcli/config": "^8.0.0", + "import-meta-resolve": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -9639,6 +10862,14 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -12088,6 +13319,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -12134,6 +13385,14 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -12189,6 +13448,37 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -12215,8 +13505,63 @@ "engines": { "node": ">=14.16" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-run-path": { @@ -12445,6 +13790,25 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -12567,6 +13931,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -12733,6 +14104,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", @@ -14240,6 +15635,17 @@ "postcss": "^8.4.31" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -14281,12 +15687,53 @@ "node": ">=6" } }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -14635,6 +16082,30 @@ "react": ">=15" } }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -15248,6 +16719,19 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -15852,6 +17336,42 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -15941,6 +17461,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -16008,6 +17551,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -16136,6 +17693,22 @@ "node": ">= 10" } }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -16364,6 +17937,20 @@ "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -16410,6 +17997,13 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -16439,72 +18033,216 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-11.2.2.tgz", + "integrity": "sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/concat-stream": "^2.0.0", + "@types/debug": "^4.0.0", + "@types/is-empty": "^1.0.0", + "@types/node": "^22.0.0", + "@types/unist": "^3.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.0.0", + "extend": "^3.0.0", + "glob": "^10.0.0", + "ignore": "^6.0.0", + "is-empty": "^1.0.0", + "is-plain-obj": "^4.0.0", + "load-plugin": "^6.0.0", + "parse-json": "^7.0.0", + "trough": "^2.0.0", + "unist-util-inspect": "^8.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-reporter": "^8.0.0", + "vfile-statistics": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/unified-engine/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unified-engine/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 4" } }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "node_modules/unified-engine/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/unified-engine/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "license": "MIT", + "node_modules/unified-engine/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "node_modules/unified-engine/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, "engines": { - "node": ">=4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" + "node_modules/unified-engine/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/unique-string": { @@ -16522,6 +18260,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-inspect": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-8.1.0.tgz", + "integrity": "sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -16870,6 +18622,56 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -16927,6 +18729,131 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-reporter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-8.1.1.tgz", + "integrity": "sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/supports-color": "^8.0.0", + "string-width": "^6.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-sort": "^4.0.0", + "vfile-statistics": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/vfile-sort": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-4.0.0.tgz", + "integrity": "sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-3.0.0.tgz", + "integrity": "sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -17397,6 +19324,17 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "license": "MIT" }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -17414,6 +19352,47 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -17522,6 +19501,19 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/docusaurus/package.json b/docusaurus/package.json index f3a9f64e..c9fb7884 100644 --- a/docusaurus/package.json +++ b/docusaurus/package.json @@ -24,6 +24,8 @@ "@docusaurus/module-type-aliases": "3.8.1", "@docusaurus/tsconfig": "3.8.1", "@docusaurus/types": "3.8.1", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "typescript": "~5.8.3" }, "browserslist": { diff --git a/docusaurus/sidebars.ts b/docusaurus/sidebars.ts index 4e14e296..560c2f9f 100644 --- a/docusaurus/sidebars.ts +++ b/docusaurus/sidebars.ts @@ -1,40 +1,124 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [ + documentationSidebar: [ + { + type: 'doc', + id: 'index', + label: 'Documentation Home', + }, { type: 'doc', id: 'overview', }, { type: 'category', - label: 'STIX Domain Objects', + label: 'Tutorials', + description: 'Learning-oriented guides for getting started', + link: { type: 'doc', id: 'tutorials/index' }, items: [ - { - type: 'autogenerated', - dirName: 'sdo', - }, + 'tutorials/your-first-query', + 'tutorials/technique-browser', + 'tutorials/relationships', + 'tutorials/multi-domain-analysis', ], }, { type: 'category', - label: 'STIX Relationship Objects', + label: 'How-to Guides', + description: 'Problem-oriented solutions for specific tasks', + link: { type: 'doc', id: 'how-to-guides/index' }, items: [ - { - type: 'autogenerated', - dirName: 'sro', - }, + 'how-to-guides/manage-data-sources', + 'how-to-guides/validate-bundles', + 'how-to-guides/error-handling', + 'how-to-guides/performance', ], }, { type: 'category', - label: 'STIX Meta Objects', + label: 'Reference', + description: 'Information-oriented technical specifications', + link: { type: 'doc', id: 'reference/index' }, items: [ { - type: 'autogenerated', - dirName: 'smo', + type: 'category', + label: 'API Documentation', + link: { type: 'doc', id: 'reference/api/index' }, + items: [ + 'reference/api/data-sources', + 'reference/api/attack-data-model', + 'reference/api/utilities', + ], }, + { + type: 'category', + label: 'Schema Reference', + link: { type: 'doc', id: 'reference/schemas/index' }, + items: [ + 'reference/schemas/stix-bundle.schema', + { + type: 'category', + label: 'STIX Domain Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/sdo', + }, + ], + }, + { + type: 'category', + label: 'STIX Relationship Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/sro', + }, + ], + }, + { + type: 'category', + label: 'STIX Meta Objects', + items: [ + { + type: 'autogenerated', + dirName: 'reference/schemas/smo', + }, + ], + }, + ], + }, + 'reference/errors', + 'reference/configuration', + ], + }, + { + type: 'category', + label: 'Principles', + description: 'Understanding-oriented context and design rationale', + link: { type: 'doc', id: 'principles/index' }, + items: [ + 'principles/why-adm-exists', + 'principles/why-zod', + 'principles/stix-foundation', + 'principles/attack-specification-overview', + 'principles/versioning-philosophy', + 'principles/schema-design', + 'principles/compatibility', + 'principles/trade-offs', + ], + }, + { + type: 'category', + label: 'Contributing', + description: 'How to contribute to the project', + link: { type: 'doc', id: 'contributing/index' }, + items: [ + 'contributing/dev-setup', + 'contributing/coding-style', + 'contributing/tests', + 'contributing/docs', ], }, ], diff --git a/docusaurus/src/components/DocTypeIndicator/index.tsx b/docusaurus/src/components/DocTypeIndicator/index.tsx new file mode 100644 index 00000000..9156c8d8 --- /dev/null +++ b/docusaurus/src/components/DocTypeIndicator/index.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './styles.module.css'; + +export type DocType = 'tutorial' | 'how-to' | 'reference' | 'principles'; + +export interface DocTypeIndicatorProps { + type: DocType; + size?: 'small' | 'medium' | 'large'; + showLabel?: boolean; + className?: string; +} + +const docTypeConfig = { + tutorial: { + emoji: '๐ŸŽ“', + label: 'Tutorial', + description: 'Learning-oriented', + color: '#10b981', // emerald-500 + bgColor: '#d1fae5', // emerald-100 + }, + 'how-to': { + emoji: '๐Ÿ”ง', + label: 'How-to Guide', + description: 'Problem-oriented', + color: '#f59e0b', // amber-500 + bgColor: '#fef3c7', // amber-100 + }, + reference: { + emoji: '๐Ÿ“–', + label: 'Reference', + description: 'Information-oriented', + color: '#3b82f6', // blue-500 + bgColor: '#dbeafe', // blue-100 + }, + principles: { + emoji: '๐Ÿ’ก', + label: 'Principles', + description: 'Understanding-oriented', + color: '#8b5cf6', // violet-500 + bgColor: '#ede9fe', // violet-100 + }, +}; + +export default function DocTypeIndicator({ + type, + size = 'medium', + showLabel = true, + className, +}: DocTypeIndicatorProps): JSX.Element { + const config = docTypeConfig[type]; + + return ( +
+ + {config.emoji} + + {showLabel && ( +
+ {config.label} + {config.description} +
+ )} +
+ ); +} + +export function DocTypeBadge({ + type, + className, +}: { + type: DocType; + className?: string; +}): JSX.Element { + return ( + + ); +} + +export function DocTypeCard({ + type, + title, + description, + href, + className, +}: { + type: DocType; + title: string; + description: string; + href: string; + className?: string; +}): JSX.Element { + const config = docTypeConfig[type]; + + return ( + +
+ +
+

{title}

+ {config.label} +
+
+

{description}

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

{description}

+
+
+

{title}

+

{description}

); @@ -63,14 +62,76 @@ function Feature({ title, description }: FeatureItem) { export default function HomepageFeatures(): JSX.Element { return ( -
-
-
- {FeatureList.map((props, idx) => ( - - ))} + <> + {/* Documentation Navigation Section */} +
+
+
+ + Let's Get Started + +

+ This is the official documentation for the ATT&CK Data Model library. +

+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
-
-
+ + + {/* Quick Start Section */} +
+
+
+ + Quick Start + +

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

+
+
+
+
+
+

Install

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

Import and Use

+
{`import { registerDataSource, loadDataModel } from '@mitre-attack/attack-data-model';
+
+const uuid = await registerDataSource(dataSource);
+const attackModel = loadDataModel(uuid);`}
+
+
+
+ +
+
+
+ ); } diff --git a/docusaurus/src/components/HomepageFeatures/styles.module.css b/docusaurus/src/components/HomepageFeatures/styles.module.css index b248eb2e..ecdfcb54 100644 --- a/docusaurus/src/components/HomepageFeatures/styles.module.css +++ b/docusaurus/src/components/HomepageFeatures/styles.module.css @@ -1,11 +1,214 @@ -.features { - display: flex; +/* Section styles */ +.documentationSection { + padding: 4rem 0; + background: var(--ifm-background-surface-color); +} + +.quickStartSection { + padding: 4rem 0; + /* background: var(--ifm-background-surface-color); */ + background: var(--ifm-color-emphasis-100); +} + +/* Common section elements */ +.sectionTitle { + margin-bottom: 1rem; + font-size: 2.5rem; + font-weight: 700; + color: var(--ifm-heading-color); +} + +.sectionSubtitle { + font-size: 1.125rem; + color: var(--ifm-color-emphasis-700); + max-width: 600px; + margin: 0 auto; + line-height: 1.6; +} + +/* Documentation cards grid */ +.docTypeGrid { + gap: 2rem; + margin-top: 2rem; +} + +.docTypeCard { + height: 100%; + margin-bottom: 2rem; +} + +/* Key features */ +.keyFeature { + margin-bottom: 2rem; +} + +.keyFeatureContent { + text-align: center; + padding: 1.5rem; + height: 100%; + border-radius: 0.75rem; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.keyFeatureContent:hover { + transform: translateY(-4px); + box-shadow: 0 8px 25px -8px var(--ifm-color-emphasis-400); +} + +.keyFeatureTitle { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--ifm-heading-color); +} + +.keyFeatureDescription { + color: var(--ifm-color-emphasis-700); + line-height: 1.5; + margin: 0; +} + +/* Quick start section */ +.quickStartContent { + max-width: 900px; + margin: 0 auto; +} + +.codeBlock { + background: var(--ifm-code-background); + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 0.5rem; + padding: 1.5rem; + height: 100%; +} + +.codeBlock h4 { + margin: 0 0 1rem 0; + color: var(--ifm-heading-color); + font-weight: 600; +} + +.codeBlock pre { + margin: 0; + padding: 0; + background: transparent; + border: none; + font-size: 0.875rem; + line-height: 1.5; +} + +.codeBlock code { + background: transparent; + padding: 0; + border-radius: 0; + color: var(--ifm-color-emphasis-800); + font-family: var(--ifm-font-family-monospace); +} + +.primaryButton { + display: inline-flex; align-items: center; - padding: 2rem 0; - width: 100%; + padding: 1rem 2rem; + background: var(--ifm-color-primary); + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 600; + font-size: 1.125rem; + transition: all 0.2s ease; +} + +.primaryButton:hover { + background: var(--ifm-color-primary-dark); + color: white; + text-decoration: none; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.primaryButton:active { + transform: translateY(0); +} + +/* Dark theme adjustments */ +[data-theme='dark'] .keyFeatureContent { + background: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .keyFeatureContent:hover { + box-shadow: 0 8px 25px -8px rgba(0, 0, 0, 0.4); +} + +[data-theme='dark'] .codeBlock { + background: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-400); +} + +/* Responsive design */ +@media (max-width: 996px) { + .sectionTitle { + font-size: 2rem; + } + + .documentationSection, + .quickStartSection { + padding: 3rem 0; + } + + .docTypeGrid { + gap: 1.5rem; + } + + .keyFeatureContent { + padding: 1.25rem; + } +} + +@media (max-width: 768px) { + .sectionTitle { + font-size: 1.75rem; + } + + .sectionSubtitle { + font-size: 1rem; + } + + .documentationSection, + .quickStartSection { + padding: 2rem 0; + } + + .codeBlock { + padding: 1.25rem; + } + + .codeBlock h4 { + font-size: 1rem; + } + + .primaryButton { + padding: 0.875rem 1.5rem; + font-size: 1rem; + } } -.featureSvg { - height: 200px; - width: 200px; +/* Focus styles for accessibility */ +.primaryButton:focus, +.keyFeatureContent:focus { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .keyFeatureContent { + border-width: 2px; + } + + .codeBlock { + border-width: 2px; + } } diff --git a/docusaurus/src/components/WorkInProgressNotice.tsx b/docusaurus/src/components/WorkInProgressNotice.tsx new file mode 100644 index 00000000..b259f8b2 --- /dev/null +++ b/docusaurus/src/components/WorkInProgressNotice.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import Admonition from '@theme/Admonition'; + +export default function WorkInProgressNotice() { + return ( + + This document is a work in progress. Content may change, and some sections may be incomplete. + + ); +} diff --git a/docusaurus/src/css/custom.css b/docusaurus/src/css/custom.css index 51135768..8fc91ec4 100644 --- a/docusaurus/src/css/custom.css +++ b/docusaurus/src/css/custom.css @@ -5,6 +5,7 @@ */ :root { + /* Primary brand colors */ --ifm-color-primary: #c64227; --ifm-color-primary-dark: #b64227; --ifm-color-primary-darker: #a64227; @@ -12,11 +13,50 @@ --ifm-color-primary-light: #d64227; --ifm-color-primary-lighter: #e64227; --ifm-color-primary-lightest: #f64227; + + /* Documentation type colors */ + --doc-tutorial-color: #10b981; + --doc-tutorial-bg: #d1fae5; + --doc-tutorial-border: #6ee7b7; + + --doc-howto-color: #f59e0b; + --doc-howto-bg: #fef3c7; + --doc-howto-border: #fcd34d; + + --doc-reference-color: #3b82f6; + --doc-reference-bg: #dbeafe; + --doc-reference-border: #93c5fd; + + --doc-explanation-color: #8b5cf6; + --doc-explanation-bg: #ede9fe; + --doc-explanation-border: #c4b5fd; + + /* Enhanced typography */ + --ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', 'Roboto', 'Ubuntu', 'Cantarell', 'Noto Sans', sans-serif; + --ifm-font-family-monospace: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace; + + /* Code styling */ --ifm-code-font-size: 95%; + --ifm-code-padding-horizontal: 0.375rem; + --ifm-code-padding-vertical: 0.125rem; + --ifm-code-border-radius: 0.375rem; + + /* Enhanced spacing */ + --ifm-spacing-horizontal: 1.5rem; + --ifm-global-spacing: 1.5rem; + + /* Improved line heights */ + --ifm-line-height-base: 1.6; + --ifm-heading-line-height: 1.3; + + /* Code block styling */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --ifm-pre-padding: 1.5rem; + --ifm-pre-border-radius: 0.5rem; } [data-theme='dark'] { + /* Primary brand colors for dark theme */ --ifm-color-primary: #c64227; --ifm-color-primary-dark: #b64227; --ifm-color-primary-darker: #a64227; @@ -24,5 +64,252 @@ --ifm-color-primary-light: #d64227; --ifm-color-primary-lighter: #e64227; --ifm-color-primary-lightest: #f64227; + + /* Documentation type colors for dark theme */ + --doc-tutorial-color: #34d399; + --doc-tutorial-bg: #064e3b; + --doc-tutorial-border: #047857; + + --doc-howto-color: #fbbf24; + --doc-howto-bg: #451a03; + --doc-howto-border: #92400e; + + --doc-reference-color: #60a5fa; + --doc-reference-bg: #1e3a8a; + --doc-reference-border: #1d4ed8; + + --doc-explanation-color: #a78bfa; + --doc-explanation-bg: #4c1d95; + --doc-explanation-border: #6d28d9; + + /* Code block styling for dark theme */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); -} \ No newline at end of file +} + +/* Enhanced typography */ +body { + font-family: var(--ifm-font-family-base); + line-height: var(--ifm-line-height-base); + font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1; +} + +h1, h2, h3, h4, h5, h6 { + line-height: var(--ifm-heading-line-height); + font-weight: 600; + letter-spacing: -0.025em; +} + +/* Improved code styling */ +code { + font-family: var(--ifm-font-family-monospace); + font-weight: 500; + font-feature-settings: 'calt' 1; +} + +pre code { + font-weight: 400; +} + +/* Enhanced admonitions */ +.admonition { + border-radius: 0.5rem; + border-left-width: 4px; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +/* Documentation type-specific styling */ +.doc-tutorial { + border-left-color: var(--doc-tutorial-color); + background: var(--doc-tutorial-bg); +} + +.doc-howto { + border-left-color: var(--doc-howto-color); + background: var(--doc-howto-bg); +} + +.doc-reference { + border-left-color: var(--doc-reference-color); + background: var(--doc-reference-bg); +} + +.doc-explanation { + border-left-color: var(--doc-explanation-color); + background: var(--doc-explanation-bg); +} + +/* Enhanced table styling */ +table { + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +th { + font-weight: 600; + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.05em; +} + +/* Improved blockquote styling */ +blockquote { + border-left-color: var(--ifm-color-primary); + border-left-width: 4px; + border-radius: 0 0.375rem 0.375rem 0; + background: var(--ifm-color-emphasis-100); + margin: 1.5rem 0; + padding: 1rem 1.5rem; + font-style: normal; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +/* Enhanced button styling */ +.button { + font-weight: 500; + border-radius: 0.375rem; + transition: all 0.2s ease; +} + +.button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.button--primary { + background: var(--ifm-color-primary); +} + +.button--primary:hover { + background: var(--ifm-color-primary-dark); +} + +/* Enhanced card styling */ +.card { + border-radius: 0.75rem; + border: 1px solid var(--ifm-color-emphasis-200); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + transition: all 0.2s ease; +} + +.card:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + transform: translateY(-2px); +} + +/* Improved navbar styling */ +.navbar { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +/* Enhanced sidebar styling */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--ifm-color-emphasis-200); +} + +.theme-doc-sidebar-item-category { + margin-bottom: 0.5rem; +} + +.theme-doc-sidebar-item-link { + border-radius: 0.375rem; + transition: all 0.15s ease; +} + +.theme-doc-sidebar-item-link:hover { + background: var(--ifm-color-emphasis-100); +} + +.theme-doc-sidebar-item-link-level-1.theme-doc-sidebar-item-link--active { + background: var(--ifm-color-primary); + color: white; +} + +/* Improved code block styling */ +.prism-code { + border-radius: var(--ifm-pre-border-radius); + font-family: var(--ifm-font-family-monospace); + font-weight: 400; + font-size: 0.875rem; + line-height: 1.5; +} + +/* Enhanced search styling */ +.DocSearch-Button { + border-radius: 0.5rem; + transition: all 0.2s ease; +} + +.DocSearch-Button:hover { + background: var(--ifm-color-emphasis-200); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Utility classes for documentation types */ +.tutorial-accent { + color: var(--doc-tutorial-color); +} + +.howto-accent { + color: var(--doc-howto-color); +} + +.reference-accent { + color: var(--doc-reference-color); +} + +.explanation-accent { + color: var(--doc-explanation-color); +} + +/* Responsive improvements */ +@media (max-width: 768px) { + :root { + --ifm-spacing-horizontal: 1rem; + --ifm-global-spacing: 1rem; + --ifm-pre-padding: 1rem; + } + + blockquote { + margin: 1rem 0; + padding: 0.75rem 1rem; + } + + .admonition { + margin: 1rem 0; + } +} + +/* Accessibility improvements */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .card, + .admonition, + table { + border-width: 2px; + } + + .button { + border-width: 2px; + } +} + +/* Focus improvements for accessibility */ +:focus-visible { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; +} diff --git a/docusaurus/src/pages/index.tsx b/docusaurus/src/pages/index.tsx index 93be1c6c..438c59f7 100644 --- a/docusaurus/src/pages/index.tsx +++ b/docusaurus/src/pages/index.tsx @@ -13,13 +13,12 @@ function HomepageHeader() {
- {siteConfig.title} -

Documentation

+

MITRE ATT&CKยฎ Data Model

- - Browse ATT&CK Schemas + + Get Started
diff --git a/eslint.config.js b/eslint.config.js index d5fe1a02..1a265844 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,31 @@ -import globals from "globals"; -import pluginJs from "@eslint/js"; -import tseslint from "typescript-eslint"; +import pluginJs from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; - +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import * as mdx from 'eslint-plugin-mdx'; export default [ - { files: ["**/*.{js,mjs,cjs,ts}"] }, - { languageOptions: { globals: globals.browser } }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - eslintPluginPrettierRecommended -]; \ No newline at end of file + { + ignores: ['node_modules', 'dist', 'docusaurus'], + }, + { files: ['**/*.{js,mjs,cjs,ts}'] }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + eslintPluginPrettierRecommended, + + // MDX file support (in the documentation) + { + files: ['**/*.mdx'], + processor: mdx.processors.mdx, + }, + { + files: ['**/*.{md,mdx}'], + plugins: { + mdx, + }, + rules: { + 'mdx/no-unused-expressions': 'error', + }, + }, +]; diff --git a/examples/README.md b/examples/README.md index cebab827..9d3649b8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,9 +1,10 @@ # Examples -This directory contains example TypeScript modules demonstrating common use cases and expected outputs for true-positive and true-negative scenarios, adapted for each of the provided ATT&CK Zod schemas. +This directory contains example TypeScript modules demonstrating common use cases for using the ATT&CK Data Model. +Read more about them in the documentation! To run them, run `npx tsx` from the project root directory: ```bash -npx tsx examples/sdo/tactic.example.ts -``` \ No newline at end of file +npx tsx examples/first-query.ts +``` diff --git a/examples/common/x-mitre-domains.example.ts b/examples/common/x-mitre-domains.example.ts deleted file mode 100644 index 5dc640d4..00000000 --- a/examples/common/x-mitre-domains.example.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { z } from 'zod'; -import { xMitreDomainsSchema } from '../../src/schemas/common/index.js'; - - -const ExampleObjectSchema = z.object({ - x_mitre_domains: xMitreDomainsSchema -}); - -// Test function for individual arrays -function testArrayCase(testCase: any[]) { - console.log(`Testing array: ${JSON.stringify(testCase)}`); - try { - const result = xMitreDomainsSchema.parse(testCase); - console.log(`SUCCESS: Parsed ${JSON.stringify(result)}`); - } catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } else { - console.log("Unexpected error:", error); - } - } - console.log("---"); -} - -// Test function for objects -function testObjectCase(testCase: any) { - console.log(`Testing object: ${JSON.stringify(testCase)}`); - try { - ExampleObjectSchema.parse(testCase); - console.log(`SUCCESS: Parsed ${JSON.stringify(testCase)}`); - } catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } else { - console.log("Unexpected error:", error); - } - } - console.log("---"); -} - -// Main test function -function runTests() { - console.log("Testing individual arrays:"); - const arrayCases = [ - ["enterprise-attack"], - ["foobar"], - ["enterprise-attack", "mobile-attack"], - ["enterprise-attack", "foobar"], - [] - ]; - arrayCases.forEach(testArrayCase); - - console.log("\nTesting objects:"); - const objectCases = [ - { x_mitre_domains: ["enterprise-attack"] }, - { x_mitre_domains: ["foobar"] }, - { x_mitre_domains: ["enterprise-attack", "mobile-attack"] }, - { x_mitre_domains: ["enterprise-attack", "foobar"] }, - { x_mitre_domains: [] }, - {} - ]; - objectCases.forEach(testObjectCase); -} - -// Run the tests -runTests(); \ No newline at end of file diff --git a/examples/first-query.ts b/examples/first-query.ts new file mode 100644 index 00000000..d242aad0 --- /dev/null +++ b/examples/first-query.ts @@ -0,0 +1,74 @@ +import { + registerDataSource, + loadDataModel, + DataSourceRegistration, +} from '@mitre-attack/attack-data-model'; + +async function exploreAttackData() { + console.log('Loading ATT&CK Enterprise data...\n'); + + // Step 1: Create a data source + const dataSource = new DataSourceRegistration({ + source: 'attack', // Load from official ATT&CK repository + domain: 'enterprise-attack', // Focus on Enterprise domain + version: '17.1', // Use specific version for consistency + parsingMode: 'relaxed', // Continue even if some data has minor issues + }); + + try { + // Step 2: Register the data source + const uuid = await registerDataSource(dataSource); + + if (uuid) { + console.log('Data source registered successfully!\n'); + + // Step 3: Load the data model + const attackDataModel = loadDataModel(uuid); + console.log(`Loaded ${attackDataModel.techniques.length} techniques\n`); + + // Step 4: Explore the first few techniques + console.log('First 5 techniques:'); + attackDataModel.techniques.slice(0, 5).forEach((technique, index) => { + console.log( + `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`, + ); + }); + console.log('\nExamining a specific technique:'); + const firstTechnique = attackDataModel.techniques[0]; + + console.log(`Name: ${firstTechnique.name}`); + console.log(`ID: ${firstTechnique.external_references[0].external_id}`); + console.log(`Description: ${firstTechnique.description.substring(0, 100)}...`); + + // Check if it's a subtechnique + if (firstTechnique.x_mitre_is_subtechnique) { + console.log('This is a sub-technique'); + } else { + console.log('This is a parent technique'); + } + console.log('\nAssociated tactics:'); + const killChainPhases = firstTechnique.kill_chain_phases || []; + const tacticShortnames = killChainPhases + .map(phase => phase.phase_name); + + const associatedTactics = attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + + if (associatedTactics.length > 0) { + associatedTactics.forEach(tactic => { + console.log(`- ${tactic.name}: ${tactic.description.replace(/\n/g, ' ').substring(0, 60)}...`); + }); + } else { + console.log('No tactics associated with this technique.'); + } + } else { + console.error('Failed to register data source'); + } + } catch (error) { + console.error('Error:', error); + } +} + +// Run the function +exploreAttackData(); diff --git a/examples/multi-domain.ts b/examples/multi-domain.ts new file mode 100644 index 00000000..4d5cff68 --- /dev/null +++ b/examples/multi-domain.ts @@ -0,0 +1,171 @@ +import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + +async function loadAllDomains() { + console.log('Loading all ATT&CK domains...\n'); + + // Define all three domains + type DomainName = "enterprise-attack" | "mobile-attack" | "ics-attack"; + const domains: { name: DomainName; label: string }[] = [ + { name: 'enterprise-attack', label: 'Enterprise' }, + { name: 'mobile-attack', label: 'Mobile' }, + { name: 'ics-attack', label: 'ICS' } + ]; + + const dataModels: { [key: string]: any } = {}; + + for (const domain of domains) { + try { + console.log(`Loading ${domain.label} domain...`); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: domain.name, + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (uuid) { + dataModels[domain.name] = loadDataModel(uuid); + const techniqueCount = dataModels[domain.name].techniques.length; + console.log(`${domain.label}: ${techniqueCount} techniques loaded\n`); + } + + } catch (error) { + console.error(`Failed to load ${domain.label} domain:`, error); + } + } + + return dataModels; +} + +function analyzeDomainStatistics(dataModels: { [key: string]: any }) { + console.log('Domain Comparison:\n'); + + const stats: { [key: string]: any } = {}; + + Object.keys(dataModels).forEach(domain => { + const model = dataModels[domain]; + stats[domain] = { + techniques: model.techniques.length, + tactics: model.tactics.length, + groups: model.groups.length, + software: model.malware.length + model.tools.length, + mitigations: model.mitigations.length + }; + }); + + // Display comparison table + console.log('| Metric | Enterprise | Mobile | ICS |'); + console.log('|-------------|------------|--------|-----|'); + + const metrics = ['techniques', 'tactics', 'groups', 'software', 'mitigations']; + + metrics.forEach(metric => { + const enterprise = stats['enterprise-attack']?.[metric] || 0; + const mobile = stats['mobile-attack']?.[metric] || 0; + const ics = stats['ics-attack']?.[metric] || 0; + + console.log(`| ${metric.padEnd(11)} | ${String(enterprise).padEnd(10)} | ${String(mobile).padEnd(6)} | ${String(ics).padEnd(3)} |`); + }); + + console.log('\n'); + return stats; +} + +function findCommonTactics(dataModels: { [key: string]: any }) { + console.log('Tactic Analysis:\n'); + + const tacticsByDomain: { [key: string]: Set } = {}; + + // Collect tactics from each domain + Object.keys(dataModels).forEach(domain => { + tacticsByDomain[domain] = new Set(); + dataModels[domain].tactics.forEach((tactic: any) => { + tacticsByDomain[domain].add(tactic.x_mitre_shortname); + }); + }); + + // Find common tactics + const allTactics = new Set(); + Object.values(tacticsByDomain).forEach(domainTactics => { + domainTactics.forEach(tactic => allTactics.add(tactic)); + }); + + console.log('Common tactics across domains:'); + + allTactics.forEach(tactic => { + const domains = Object.keys(tacticsByDomain).filter(domain => + tacticsByDomain[domain].has(tactic) + ); + + if (domains.length > 1) { + const domainLabels = domains.map(d => { + switch(d) { + case 'enterprise-attack': return 'Enterprise'; + case 'mobile-attack': return 'Mobile'; + case 'ics-attack': return 'ICS'; + default: return d; + } + }).join(', '); + + console.log(`- ${tactic}: ${domainLabels}`); + } + }); + + console.log('\n'); +} + +function analyzeCrossDomainTechniques(dataModels: { [key: string]: any }) { + console.log('Cross-Domain Technique Analysis:\n'); + + // Look for techniques with similar names across domains + const enterpriseTechniques = dataModels['enterprise-attack']?.techniques || []; + const mobileTechniques = dataModels['mobile-attack']?.techniques || []; + + console.log('Similar techniques between Enterprise and Mobile:'); + + let similarCount = 0; + + enterpriseTechniques.forEach((entTechnique: any) => { + mobileTechniques.forEach((mobTechnique: any) => { + // Simple name similarity check + const entName = entTechnique.name.toLowerCase(); + const mobName = mobTechnique.name.toLowerCase(); + + if (entName === mobName) { + console.log(`${entTechnique.name}`); + console.log(` Enterprise: ${entTechnique.external_references[0].external_id}`); + console.log(` Mobile: ${mobTechnique.external_references[0].external_id}\n`); + similarCount++; + } + }); + }); + + console.log(`Found ${similarCount} techniques with identical names across domains.\n`); +} + +async function performMultiDomainAnalysis() { + try { + // Load all domains + const dataModels = await loadAllDomains(); + + if (Object.keys(dataModels).length === 0) { + console.error('No domains loaded successfully'); + return; + } + + // Perform various analyses + analyzeDomainStatistics(dataModels); + findCommonTactics(dataModels); + analyzeCrossDomainTechniques(dataModels); + + console.log('Multi-domain analysis complete!'); + + } catch (error) { + console.error('Analysis failed:', error); + } +} + +// Run the analysis +performMultiDomainAnalysis(); \ No newline at end of file diff --git a/examples/relationship-explorer.ts b/examples/relationship-explorer.ts new file mode 100644 index 00000000..ffce549a --- /dev/null +++ b/examples/relationship-explorer.ts @@ -0,0 +1,295 @@ +import { registerDataSource, loadDataModel, DataSourceRegistration, AttackDataModel, TechniqueImpl } from '@mitre-attack/attack-data-model'; + +class RelationshipExplorer { + private attackDataModel: AttackDataModel; + + async initialize(): Promise { + console.log('Initializing ATT&CK Relationship Explorer...\n'); + + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed' + }); + + const uuid = await registerDataSource(dataSource); + if (!uuid) throw new Error('Failed to register data source'); + + this.attackDataModel = loadDataModel(uuid); + console.log('Data loaded successfully!\n'); + } + + // We'll add exploration methods here... + + // Returns an array of tactic objects for a given technique + getTechniqueTactics(technique: any): any[] { + const killChainPhases = technique.kill_chain_phases || []; + const tacticShortnames = killChainPhases.map(phase => phase.phase_name); + + const tactics = this.attackDataModel.tactics.filter( + tactic => tacticShortnames.includes(tactic.x_mitre_shortname) + ); + return tactics; + } + + exploreTechniqueTactics(): void { + console.log('TECHNIQUE-TACTIC RELATIONSHIPS\n'); + + // Find a specific technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1055' + ) as TechniqueImpl | undefined; + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 100)}...\n`); + + const tactics = this.getTechniqueTactics(technique); + console.log(`This technique is used for ${tactics.length} tactical goal(s):`); + + tactics.forEach((tactic, index) => { + console.log(`${index + 1}. ${tactic.name}`); + console.log(` Purpose: ${tactic.description.substring(0, 80)}...\n`); + }); + + console.log('This shows how one technique can serve multiple adversary goals!\n'); + } + + exploreGroupTechniques(): void { + console.log('GROUP-TECHNIQUE RELATIONSHIPS (Procedures)\n'); + + // Find APT1 group + const apt1 = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0006' + ); + + if (!apt1) return; + + console.log(`Group: ${apt1.name} (${apt1.external_references[0].external_id})`); + console.log(`Description: ${apt1.description.substring(0, 120)}...\n`); + + // Get techniques used by this group + const techniques = apt1.getTechniques(); + console.log(`This group uses ${techniques.length} different techniques:`); + + // Show first 5 techniques + techniques.slice(0, 5).forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Find the relationship to get procedure details + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === apt1.id && + rel.target_ref === technique.id && + rel.relationship_type === 'uses' + ); + + if (relationship && relationship.description) { + console.log(` Procedure: ${relationship.description.substring(0, 100)}...`); + } + console.log(''); + }); + + console.log(`... and ${techniques.length - 5} more techniques\n`); + } + + exploreSoftwareUsage(): void { + console.log('SOFTWARE-TECHNIQUE RELATIONSHIPS\n'); + + // Find Mimikatz (a well-known tool) + const mimikatz = this.attackDataModel.tools.find(tool => + tool.name.toLowerCase().includes('mimikatz') + ); + + if (!mimikatz) return; + + console.log(`Tool: ${mimikatz.name} (${mimikatz.external_references[0].external_id})`); + console.log(`Description: ${mimikatz.description.substring(0, 120)}...\n`); + + // Get techniques used by this software + const techniques = mimikatz.getTechniques(); + console.log(`This tool implements ${techniques.length} techniques:`); + + techniques.forEach((technique, index) => { + console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + + console.log(` Supports tactics: ${tactics.map(t => t.name).join(', ')}\n`); + }); + const relationships = this.attackDataModel.relationships; + + const mimikatzId = mimikatz.id; + + // Find all "uses" relationships where target is Mimikatz + const groupUsesMimikatz = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.target_ref === mimikatzId && + rel.source_ref.startsWith("intrusion-set--") // group id prefix + ); + + // Get group objects + const groupsUsingMimikatz = groupUsesMimikatz.map(rel => + this.attackDataModel.groups.find(group => group.id === rel.source_ref) + ).filter(Boolean); // Remove undefined if any + + console.log(`This tool is used by ${groupsUsingMimikatz.length} groups:`); + groupsUsingMimikatz.slice(0, 3).forEach((group, index) => { + console.log(`${index + 1}. ${group.name} (${group.external_references[0].external_id})`); + }); + console.log(''); + } + + exploreSubtechniqueRelationships(): void { + console.log('PARENT-SUBTECHNIQUE RELATIONSHIPS\n'); + + // Find a parent technique with sub-techniques + const parentTechnique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1003' && + !t.x_mitre_is_subtechnique + ) as TechniqueImpl | undefined; + + if (!parentTechnique) return; + + console.log(`Parent Technique: ${parentTechnique.name} (${parentTechnique.external_references[0].external_id})`); + console.log(`Description: ${parentTechnique.description.substring(0, 120)}...\n`); + + // Get sub-techniques + + const subTechniques = parentTechnique.getSubTechniques(); + console.log(`This technique has ${subTechniques.length} sub-techniques:`); + + subTechniques.forEach((subTech, index) => { + console.log(`${index + 1}. ${subTech.name} (${subTech.external_references[0].external_id})`); + console.log(` Platforms: ${subTech.x_mitre_platforms?.join(', ') || 'Not specified'}\n`); + }); + + // Navigate back from sub-technique to parent + if (subTechniques.length > 0) { + const firstSubTech = subTechniques[0]; + const parentFromChild = firstSubTech.getParentTechnique(); + + console.log(`Navigation verification:`); + console.log(`Sub-technique "${firstSubTech.name}" โ†’ Parent: "${parentFromChild?.name}"`); + console.log(`Bidirectional navigation works!\n`); + } + } + + exploreMitigationRelationships(): void { + console.log('MITIGATION-TECHNIQUE RELATIONSHIPS\n'); + + // Find a technique + const technique = this.attackDataModel.techniques.find(t => + t.external_references[0].external_id === 'T1059' + ); + + if (!technique) return; + + console.log(`Technique: ${technique.name} (${technique.external_references[0].external_id})`); + console.log(`Description: ${technique.description.substring(0, 120)}...\n`); + + // Get mitigations for this technique + const mitigations = technique.getMitigations(); + console.log(`This technique can be mitigated by ${mitigations.length} measures:`); + + mitigations.forEach((mitigation, index) => { + console.log(`${index + 1}. ${mitigation.name} (${mitigation.external_references[0].external_id})`); + + // Find the relationship to get mitigation guidance + const relationship = this.attackDataModel.relationships.find(rel => + rel.source_ref === mitigation.id && + rel.target_ref === technique.id && + rel.relationship_type === 'mitigates' + ); + + if (relationship && relationship.description) { + console.log(` Guidance: ${relationship.description.substring(0, 100)}...\n`); + } + }); + } + + exploreTransitiveRelationships(): void { + console.log('TRANSITIVE RELATIONSHIPS (Group โ†’ Software โ†’ Techniques)\n'); + + // Find a group + const group = this.attackDataModel.groups.find(g => + g.external_references[0].external_id === 'G0016' + ); + + if (!group) return; + + console.log(`Group: ${group.name} (${group.external_references[0].external_id})`); + console.log(`Description: ${group.description.substring(0, 120)}...\n`); + + const relationships = this.attackDataModel.relationships; + + // Get software used by the group + const softwareUsedByGroup = relationships.filter(rel => + rel.relationship_type === "uses" && + rel.source_ref === group.id && + ( + rel.target_ref.startsWith("malware--") || + rel.target_ref.startsWith("tool--") + ) + ); + + // Get software objects + const allSoftware = [ + ...this.attackDataModel.malware, + ...this.attackDataModel.tools + ]; + const software = softwareUsedByGroup.map(rel => + allSoftware.find(soft => soft.id === rel.target_ref) + ).filter(Boolean); + + console.log(`This group uses ${software.length} software tools:`); + + software.slice(0, 3).forEach((soft, index) => { + console.log(`\n${index + 1}. ${soft.name} (${soft.external_references[0].external_id})`); + + // Get techniques used by this software + const techniques = soft.getTechniques(); + console.log(` โ†’ Implements ${techniques.length} techniques:`); + + techniques.slice(0, 2).forEach((technique, techIndex) => { + console.log(` ${techIndex + 1}. ${technique.name} (${technique.external_references[0].external_id})`); + + // Show tactics supported + // Show which tactics this technique supports + const tactics = this.getTechniqueTactics(technique); + console.log(` Tactics: ${tactics.map(t => t.name).join(', ')}`); + }); + + if (techniques.length > 2) { + console.log(` ... and ${techniques.length - 2} more techniques`); + } + }); + + console.log(`\nThis shows the relationship chain: Group โ†’ Software โ†’ Techniques โ†’ Tactics\n`); + } +} + +// Initialize and run examples +async function main() { + const explorer = new RelationshipExplorer(); + await explorer.initialize(); + + // We'll add example calls here... + await explorer.exploreTechniqueTactics(); + await explorer.exploreGroupTechniques(); + await explorer.exploreSoftwareUsage(); + await explorer.exploreSubtechniqueRelationships(); + await explorer.exploreMitigationRelationships(); + await explorer.exploreTransitiveRelationships(); + + console.log('Relationship exploration complete!\n'); + console.log('Key takeaways:'); + console.log(' - ATT&CK objects are richly interconnected'); + console.log(' - Relationships carry descriptive context'); + console.log(' - Navigation methods simplify complex queries'); + console.log(' - Transitive relationships reveal attack patterns'); +} + +main().catch(console.error); diff --git a/examples/sdo/analytic.example.ts b/examples/sdo/analytic.example.ts deleted file mode 100644 index e38c7fda..00000000 --- a/examples/sdo/analytic.example.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import { analyticSchema } from "../../src/schemas/sdo/analytic.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Analytic -/*************************************************************************************************** */ -const validAnalytic = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43d", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Suspicious PowerShell Activity", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0001", - "url": "https://attack.mitre.org/analytics/AN0001" - } - ], - "description": "Adversary execution of PowerShell commands with suspicious parameters", - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["PowerShell"] - } - ], - "x_mitre_platforms": ["Windows"], - "x_mitre_mutable_elements": [ - { - "field": "TimeWindow", - "description": "Time window for correlation analysis" - } - ] -}; - -console.log("\nExample 1 - Valid Analytic:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalytic).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Analytic (ATT&CK ID does not match format AN####) -/*************************************************************************************************** */ -const invalidAnalyticID = { - ...validAnalytic, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/analytics/AN0001", - "external_id": "A001" - } - ], -}; - -console.log("\nExample 2 - Invalid Analytic (ATT&CK ID does not match format AN####):"); -try { - analyticSchema.parse(invalidAnalyticID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– The first external_reference must match the ATT&CK ID format AN####. -// โ†’ at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Valid Analytic with Multiple Log Sources and Mutable Elements -/*************************************************************************************************** */ -const validAnalyticMultipleElements = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43e", - "name": "Multi-Layer Process Monitoring", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0002", - "url": "https://attack.mitre.org/analytics/AN0002" - } - ], - "description": "Adversary process creation and execution patterns across multiple log sources", - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["Sysmon", "EventCode=1"] - }, - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b811-9dad-11d1-80b4-00c04fd430c9", - "permutation_names": ["Security", "EventCode=4688"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "TimeWindow", - "description": "Time window for correlation analysis" - }, - { - "field": "UserContext", - "description": "User context for filtering" - }, - { - "field": "ProcessNamePattern", - "description": "Regular expression pattern for process names" - } - ] -}; - -console.log("\nExample 3 - Valid Analytic with Multiple Elements:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticMultipleElements).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Analytic (missing required fields) -/*************************************************************************************************** */ -const invalidAnalyticMissingFields = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a43f", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - // Missing name - // Missing created_by_ref (required by schema) - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // Missing object_marking_refs (required by schema) - // Missing x_mitre_domains - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0003", - "url": "https://attack.mitre.org/analytics/AN0003" - } - ], - // Missing description - // Missing x_mitre_log_source_references - // Missing x_mitre_mutable_elements -}; - -console.log("\nExample 4 - Invalid Analytic (missing required fields):"); -try { - analyticSchema.parse(invalidAnalyticMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Unrecognized key: "x_mitre_modified_by_ref" -// โœ– Invalid input: expected nonoptional, received undefined -// โ†’ at created_by_ref -// โœ– Invalid input: expected nonoptional, received undefined -// โ†’ at object_marking_refs -// โœ– Invalid input: expected string, received undefined -// โ†’ at name -// โœ– x_mitre_platforms must be an array of strings -// โ†’ at x_mitre_platforms -// โœ– Invalid input: expected string, received undefined -// โ†’ at description -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_log_source_references -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_mutable_elements -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_domains - -/*************************************************************************************************** */ -// Example 5: Invalid Analytic with invalid type -/*************************************************************************************************** */ -const analyticWithInvalidType = { - ...validAnalytic, - "type": 'invalid-type' -} - -console.log("\nExample 5 - Analytic with invalid type:"); -try { - analyticSchema.parse(analyticWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Invalid input: expected "x-mitre-analytic" -// โ†’ at type - -/*************************************************************************************************** */ -// Example 6: Invalid Analytic (empty log source references array) -/*************************************************************************************************** */ -const invalidAnalyticEmptyLogSources = { - ...validAnalytic, - "x_mitre_log_source_references": [] -}; - -console.log("\nExample 6 - Invalid Analytic (empty log source references array):"); -try { - analyticSchema.parse(invalidAnalyticEmptyLogSources); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too small: expected array to have >=1 items -// โ†’ at x_mitre_log_source_references - -/*************************************************************************************************** */ -// Example 7: Invalid Analytic (duplicate log source refs) -/*************************************************************************************************** */ -const invalidAnalyticDuplicateLogSources = { - ...validAnalytic, - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["PowerShell"] - }, - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["Security"] - } - ] -}; - -console.log("\nExample 7 - Invalid Analytic (duplicate log source refs):"); -try { - analyticSchema.parse(invalidAnalyticDuplicateLogSources); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Duplicate log source ref found: each (x_mitre_log_source_ref, permutation_names) pair must be unique -// โ†’ at x_mitre_log_source_references.x_mitre_log_source_ref - -/*************************************************************************************************** */ -// Example 8: Invalid Analytic (empty mutable elements array) -/*************************************************************************************************** */ -const invalidAnalyticEmptyMutableElements = { - ...validAnalytic, - "x_mitre_mutable_elements": [] -}; - -console.log("\nExample 8 - Invalid Analytic (empty mutable elements array):"); -try { - analyticSchema.parse(invalidAnalyticEmptyMutableElements); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too small: expected array to have >=1 items -// โ†’ at x_mitre_mutable_elements - -/*************************************************************************************************** */ -// Example 9: Invalid Analytic (empty description field) -/*************************************************************************************************** */ -const invalidAnalyticEmptyDescription = { - ...validAnalytic, - "description": "" -}; - -console.log("\nExample 9 - Invalid Analytic (empty description field):"); -try { - analyticSchema.parse(invalidAnalyticEmptyDescription); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too small: expected string to have >=1 characters -// โ†’ at description - -/*************************************************************************************************** */ -// Example 10: Invalid Analytic (empty log source reference permutation_names) -/*************************************************************************************************** */ -const invalidAnalyticEmptyPermutationNames = { - ...validAnalytic, - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": [] - } - ] -}; - -console.log("\nExample 10 - Invalid Analytic (empty log source reference permutation_names):"); -try { - analyticSchema.parse(invalidAnalyticEmptyPermutationNames); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too small: expected array to have >=1 items -// โ†’ at x_mitre_log_source_references[0].permutation_names -// โœ– Too small: expected array to have >=1 items -// โ†’ at x_mitre_log_source_references[0].permutation_names - -/*************************************************************************************************** */ -// Example 11: Valid Analytic with Platform -/*************************************************************************************************** */ -const validAnalyticWithPlatform = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a440", - "name": "Windows Process Creation Detection", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0010", - "url": "https://attack.mitre.org/analytics/AN0010" - } - ], - "x_mitre_platforms": ["Windows"], - "description": "Windows-specific process creation patterns indicating potential adversary activity" -}; - -console.log("\nExample 11 - Valid Analytic with Platform:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticWithPlatform).name}`) - -/*************************************************************************************************** */ -// Example 12: Invalid Analytic (multiple platforms) -/*************************************************************************************************** */ -const invalidAnalyticMultiplePlatforms = { - ...validAnalytic, - "x_mitre_platforms": ["Windows", "Linux"] -}; - -console.log("\nExample 12 - Invalid Analytic (multiple platforms):"); -try { - analyticSchema.parse(invalidAnalyticMultiplePlatforms); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too big: expected array to have <=1 items -// โ†’ at x_mitre_platforms - -/*************************************************************************************************** */ -// Example 13: Valid Mobile Analytic -/*************************************************************************************************** */ -const validMobileAnalytic = { - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a441", - "type": "x-mitre-analytic", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Mobile Application Permission Abuse", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["mobile-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0020", - "url": "https://attack.mitre.org/analytics/AN0020" - } - ], - "x_mitre_platforms": ["Android"], - "description": "Mobile applications requesting excessive or suspicious permissions", - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": `x-mitre-log-source--${uuidv4()}`, - "permutation_names": ["logcat", "system"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "PermissionThreshold", - "description": "Number of permissions that trigger suspicious behavior detection" - } - ] -}; - -console.log("\nExample 13 - Valid Mobile Analytic:"); -console.log(`SUCCESS ${analyticSchema.parse(validMobileAnalytic).name}`) - -/*************************************************************************************************** */ -// Example 14: Invalid Analytic (wrong log source ref STIX ID format) -/*************************************************************************************************** */ -const invalidAnalyticWrongLogSourceId = { - ...validAnalytic, - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-analytic--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["PowerShell"] - } - ] -}; - -console.log("\nExample 14 - Invalid Analytic (wrong log source ref STIX ID format):"); -try { - analyticSchema.parse(invalidAnalyticWrongLogSourceId); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Invalid STIX Identifier: must start with 'x-mitre-log-source--' -// โ†’ at x_mitre_log_source_references[0].x_mitre_log_source_ref - -/*************************************************************************************************** */ -// Example 15: Valid Analytic with Complex Log Source Permutation Names -/*************************************************************************************************** */ -const validAnalyticComplexPermutationNames = { - ...validAnalytic, - "id": "x-mitre-analytic--7c93fd1a-1e6b-4b4e-be42-0d843216a442", - "name": "Advanced Process Monitoring", - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "AN0030", - "url": "https://attack.mitre.org/analytics/AN0030" - } - ], - "description": "Complex process creation and execution patterns using multiple log sources", - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["sysmon:1", "auditd:SYSCALL", "Security/Microsoft-Windows-Security-Auditing"] - } - ], - "x_mitre_mutable_elements": [ - { - "field": "ProcessPattern", - "description": "Regular expression pattern for matching suspicious process names" - } - ] -}; - -console.log("\nExample 15 - Valid Analytic with Complex Log Source Permutation Names:"); -console.log(`SUCCESS ${analyticSchema.parse(validAnalyticComplexPermutationNames).name}`); - -/*************************************************************************************************** */ -// Example 16: Analytic with unknown property -/*************************************************************************************************** */ -const analyticWithUnknownProperty = { - ...validAnalytic, - foo: 'bar' -} - -console.log("\nExample 16 - Parsing an analytic with an unknown property (foo: 'bar'):"); -try { - const parsedAnalytic = analyticSchema.parse(analyticWithUnknownProperty); - console.log("Parsed successfully. Analytic name:", parsedAnalytic.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Unrecognized key: "foo" - -/*************************************************************************************************** */ -// Example 17: Invalid Analytic (duplicate permutation_names in same log source) -/*************************************************************************************************** */ -const invalidAnalyticDuplicateKeys = { - ...validAnalytic, - "x_mitre_log_source_references": [ - { - "x_mitre_log_source_ref": "x-mitre-log-source--6ba7b810-9dad-11d1-80b4-00c04fd430c8", - "permutation_names": ["PowerShell", "PowerShell"] - } - ] -}; - -console.log("\nExample 17 - Invalid Analytic (duplicate permutation_names in same log source):"); -try { - analyticSchema.parse(invalidAnalyticDuplicateKeys); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Duplicate log source permutation found: each (name, channel) pair must be unique -// โ†’ at x_mitre_log_source_references[0].permutation_names - -/*************************************************************************************************** */ -// Example 18: Invalid Analytic (empty mutable element field) -/*************************************************************************************************** */ -const invalidAnalyticEmptyMutableField = { - ...validAnalytic, - "x_mitre_mutable_elements": [ - { - "field": "", - "description": "Time window for correlation analysis" - } - ] -}; - -console.log("\nExample 18 - Invalid Analytic (empty mutable element field):"); -try { - analyticSchema.parse(invalidAnalyticEmptyMutableField); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Too small: expected string to have >=1 characters -// โ†’ at x_mitre_mutable_elements[0].field \ No newline at end of file diff --git a/examples/sdo/asset.example.ts b/examples/sdo/asset.example.ts deleted file mode 100644 index 32b759f5..00000000 --- a/examples/sdo/asset.example.ts +++ /dev/null @@ -1,229 +0,0 @@ - -import { z } from "zod/v4"; -import { assetSchema } from "../../src/schemas/sdo/asset.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Asset -/*************************************************************************************************** */ -const validAsset = { - "modified": "2023-10-04T18:05:43.237Z", - "name": "Remote Terminal Unit (RTU)", - "description": "A Remote Terminal Unit (RTU) is a device that typically resides between field devices (e.g., PLCs, IEDs) and control/SCADA servers and supports various communication interfacing and data aggregation functions. RTUs are typically responsible for forwarding commands from the control server and the collection of telemetry, events, and alerts from the field devices. An RTU can be implemented as a dedicated embedded device, as software platform that runs on a hardened/ruggedized computer, or using a custom application program on a PLC.", - "x_mitre_platforms": [ - "Embedded", - "Windows", - "Linux" - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "ics-attack" - ], - "x_mitre_version": "1.0", - "type": "x-mitre-asset", - "id": "x-mitre-asset--1769c499-55e5-462f-bab2-c39b8cd5ae32", - "created": "2023-09-28T14:44:54.756Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/assets/A0004", - "external_id": "A0004" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 1: Valid Asset:"); -console.log(`SUCCESS ${assetSchema.parse(validAsset).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Asset (ATT&CK ID does not match format A####) -/*************************************************************************************************** */ -const invalidAssetID = { - ...validAsset, - external_references: [ - { - source_name: "mitre-attack", - external_id: "T1111" - } - ] -}; - -console.log("\nExample 2: Invalid Asset (ATT&CK ID does not match format A####):"); -try { - assetSchema.parse(invalidAssetID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– The first external_reference must match the ATT&CK ID format A####. -// โ†’ at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Invalid Asset (missing required fields) -/*************************************************************************************************** */ -const invalidAssetMissingFields = { - "modified": "2023-10-04T18:05:43.237Z", - // Missing name - "description": "A Remote Terminal Unit (RTU) is a device that typically resides between field devices (e.g., PLCs, IEDs) and control/SCADA servers and supports various communication interfacing and data aggregation functions. RTUs are typically responsible for forwarding commands from the control server and the collection of telemetry, events, and alerts from the field devices. An RTU can be implemented as a dedicated embedded device, as software platform that runs on a hardened/ruggedized computer, or using a custom application program on a PLC.", - "x_mitre_sectors": [ - "Electric", - "Water and Wastewater", - "General" - ], - "x_mitre_platforms": [ - "Embedded", - "Windows", - "Linux" - ], - "x_mitre_deprecated": false, - // Missing x_mitre_domains - // Missing x_mitre_version (Defaults to 1.0) - "type": "x-mitre-asset", - "id": "x-mitre-asset--1769c499-55e5-462f-bab2-c39b8cd5ae32", - "created": "2023-09-28T14:44:54.756Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/assets/A0004", - "external_id": "A0004" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 3: Invalid Asset (missing required fields):"); -try { - assetSchema.parse(invalidAssetMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Invalid input: expected string, received undefined -// โ†’ at name -// โœ– Invalid input: expected string, received undefined -// โ†’ at x_mitre_version -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_domains - -/*************************************************************************************************** */ -// Example 4: Asset with invalid type -/*************************************************************************************************** */ -const assetWithInvalidType = { - ...validAsset, - type: "invalid-type" -}; - -console.log("\nExample 4: Asset with invalid type:"); -try { - assetSchema.parse(assetWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Invalid input: expected "x-mitre-asset" -// โ†’ at type - -/*************************************************************************************************** */ -// Example 5: Asset with optional fields -/*************************************************************************************************** */ -const assetWithOptionalFields = { - ...validAsset, - x_mitre_contributors: ["John Doe", "Jane Smith"], - x_mitre_sectors: [ - "Electric", - "Water and Wastewater", - "General" - ], - x_mitre_related_assets: [ - { - name: "Related Asset", - description: "Description", - related_asset_sectors: ["Electric"] - } - ] -}; - -console.log("\nExample 5: Asset with optional fields:"); -console.log("Parsed successfully:", assetSchema.safeParse(assetWithOptionalFields).success); - -// Parsed successfully: true - -/*************************************************************************************************** */ -// Example 6: Asset with invalid sectors -/*************************************************************************************************** */ -const assetWithInvalidSectors = { - ...validAsset, - x_mitre_sectors: [ - "Invalid Sector" - ] -}; - -console.log("\nExample 6: Asset with invalid sectors:"); -try { - assetSchema.parse(assetWithInvalidSectors); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Sector must be one of: Electric, Water and Wastewater, Manufacturing, Rail, Maritime, General -// โ†’ at x_mitre_sectors[0] - -/*************************************************************************************************** */ -// Example 7: Asset with invalid related assets -/*************************************************************************************************** */ -const assetWithInvalidRelatedAssets = { - ...validAsset, - x_mitre_related_assets: [ - { - description: "invalid related asset" - } - ] -}; - -console.log("\nExample 7: Asset with invalid related assets:"); -try { - assetSchema.parse(assetWithInvalidRelatedAssets); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Related asset name is required -// โ†’ at x_mitre_related_assets[0].name - -/** ************************************************************************************************* */ -// Example 8: Asset with unknown property -/** ************************************************************************************************* */ -const assetWithUnknownProperty = { - ...validAsset, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing an asset with an unknown property (foo: 'bar'):"); -try { - const parsedAsset = assetSchema.parse(assetWithUnknownProperty); - console.log("Parsed successfully. Asset name:", parsedAsset.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Unrecognized key: "foo" \ No newline at end of file diff --git a/examples/sdo/campaign.example.ts b/examples/sdo/campaign.example.ts deleted file mode 100644 index 13cf1b62..00000000 --- a/examples/sdo/campaign.example.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { z } from "zod/v4"; -import { campaignSchema } from "../../src/schemas/sdo/campaign.schema.js"; -import { stixCreatedByRefSchema, stixCreatedTimestampSchema, stixModifiedTimestampSchema } from "../../src/schemas/common/index.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Campaign -/** ************************************************************************************************* */ -const validCampaign = { - type: "campaign", - id: "campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Operation Dream Job", - x_mitre_version: "1.2", - description: "Operation Dream Job was a cyber espionage operation...", - created_by_ref: stixCreatedByRefSchema.parse("identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5"), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - object_marking_refs: ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/campaigns/C0022", - external_id: "C0022" - }, - { - "source_name": "Operation Interception", - "description": "(Citation: ESET Lazarus Jun 2020)" - }, - { - "source_name": "Operation North Star", - "description": "(Citation: McAfee Lazarus Jul 2020)(Citation: McAfee Lazarus Nov 2020)" - }, - { - "source_name": "McAfee Lazarus Nov 2020", - "description": "Beek, C. (2020, November 5). Operation North Star: Behind The Scenes. Retrieved December 20, 2021.", - "url": "https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-behind-the-scenes/" - }, - { - "source_name": "ESET Lazarus Jun 2020", - "description": "Breitenbacher, D and Osis, K. (2020, June 17). OPERATION IN(TER)CEPTION: Targeted Attacks Against European Aerospace and Military Companies. Retrieved December 20, 2021.", - "url": "https://www.welivesecurity.com/wp-content/uploads/2020/06/ESET_Operation_Interception.pdf" - }, - { - "source_name": "McAfee Lazarus Jul 2020", - "description": "Cashman, M. (2020, July 29). Operation North Star Campaign. Retrieved December 20, 2021.", - "url": "https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-a-job-offer-thats-too-good-to-be-true/?hilite=%27Operation%27%2C%27North%27%2C%27Star%27" - }, - { - "source_name": "ClearSky Lazarus Aug 2020", - "description": "ClearSky Research Team. (2020, August 13). Operation 'Dream Job' Widespread North Korean Espionage Campaign. Retrieved December 20, 2021.", - "url": "https://www.clearskysec.com/wp-content/uploads/2020/08/Dream-Job-Campaign.pdf" - }, - { - "source_name": "The Hacker News Lazarus Aug 2022", - "description": "Lakshmanan, R. (2022, August 17). North Korea Hackers Spotted Targeting Job Seekers with macOS Malware. Retrieved April 10, 2023.", - "url": "https://thehackernews.com/2022/08/north-korea-hackers-spotted-targeting.html" - } - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_deprecated: false, - revoked: false, - aliases: ["Operation Dream Job", "Operation North Star", "Operation Interception"], - first_seen: "2019-09-01T04:00:00.000Z", - last_seen: "2020-08-01T04:00:00.000Z", - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("Example 1 - Valid Campaign:"); -console.log("Parsed successfully:", campaignSchema.safeParse(validCampaign).success); - - -/** ************************************************************************************************* */ -// Example 2: Invalid Campaign (missing required fields) -/** ************************************************************************************************* */ -const invalidCampaign = { - type: "campaign", - id: "campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Invalid Campaign", - x_mitre_version: "1.0", - // Missing description - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2023-03-17T13:37:42.596Z", - modified: "2024-04-11T00:31:21.576Z", - // Missing object_marking_refs - x_mitre_domains: ["enterprise-attack"], - // Missing external_references - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_deprecated: false, - revoked: false, - aliases: [], - first_seen: "2019-09-01T04:00:00.000Z", - last_seen: "2020-08-01T04:00:00.000Z", - // Missing x_mitre_first_seen_citation - // Missing x_mitre_last_seen_citation -}; - -console.log("\nExample 2 - Invalid Campaign (missing required fields):"); -const e2 = campaignSchema.safeParse(invalidCampaign); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -// โœ– Invalid input: expected array, received undefined -// โ†’ at external_references -// โœ– Invalid input: expected nonoptional, received undefined -// โ†’ at object_marking_refs -// โœ– Invalid input: expected string, received undefined -// โ†’ at description -// โœ– Must be one or more citations in the form '(Citation: [citation name])' without any separators -// โ†’ at x_mitre_first_seen_citation -// โœ– Must be one or more citations in the form '(Citation: [citation name])' without any separators -// โ†’ at x_mitre_last_seen_citation - -/** ************************************************************************************************* */ -// Example 3: Campaign with optional fields -/** ************************************************************************************************* */ -const campaignWithOptionalFields = { - ...validCampaign, - x_mitre_contributors: ["John Doe", "Jane Smith"], -}; - -console.log("\nExample 3 - Campaign with optional fields:"); -console.log("Successfully parsed:", campaignSchema.safeParse(campaignWithOptionalFields).success); - -/** ************************************************************************************************* */ -// Example 4: Campaign with invalid type -/** ************************************************************************************************* */ -const campaignWithInvalidType = { - ...validCampaign, - type: "invalid-type", -}; - -console.log("\nExample 4 - Campaign with invalid type:"); -const e4 = campaignSchema.safeParse(campaignWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -// โœ– Invalid input: expected "campaign" -// โ†’ at type - -/** ************************************************************************************************* */ -// Example 5: Campaign with invalid dates -/** ************************************************************************************************* */ -const campaignWithInvalidDates = { - ...validCampaign, - first_seen: "2019-09-01", // Invalid date format - last_seen: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Campaign with invalid dates:"); -try { - campaignSchema.parse(campaignWithInvalidDates); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Invalid STIX timestamp format: must be an RFC3339 timestamp with a timezone specification of 'Z'. -// โ†’ at first_seen - -/** ************************************************************************************************* */ -// Example 6: Campaign with invalid citations -/** ************************************************************************************************* */ -const campaignWithInvalidCitations = { - ...validCampaign, - x_mitre_first_seen_citation: "", // Empty string - x_mitre_last_seen_citation: "(Citation: Valid Citation)", -}; - -console.log("\nExample 6 - Campaign with invalid citations:"); -try { - campaignSchema.parse(campaignWithInvalidCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Must be one or more citations in the form '(Citation: [citation name])' without any separators -// โ†’ at x_mitre_first_seen_citation - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example campaign -/** ************************************************************************************************* */ -const exampleOfRealCampaign = { - 'type': 'campaign', - 'id': 'campaign--0257b35b-93ef-4a70-80dd-ad5258e6045b', - 'spec_version': '2.1', - 'x_mitre_attack_spec_version': '3.2.0', - 'name': 'Operation Dream Job', - 'x_mitre_version': '1.2', - 'description': '[Operation Dream Job](https://attack.mitre.org/campaigns/C0022) was a cyber espionage operation likely conducted by [Lazarus Group](https://attack.mitre.org/groups/G0032) that targeted the defense, aerospace, government, and other sectors in the United States, Israel, Australia, Russia, and India. In at least one case, the cyber actors tried to monetize their network access to conduct a business email compromise (BEC) operation. In 2020, security researchers noted overlapping TTPs, to include fake job lures and code similarities, between [Operation Dream Job](https://attack.mitre.org/campaigns/C0022), Operation North Star, and Operation Interception; by 2022 security researchers described [Operation Dream Job](https://attack.mitre.org/campaigns/C0022) as an umbrella term covering both Operation Interception and Operation North Star.(Citation: ClearSky Lazarus Aug 2020)(Citation: McAfee Lazarus Jul 2020)(Citation: ESET Lazarus Jun 2020)(Citation: The Hacker News Lazarus Aug 2022)', - 'created_by_ref': 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - 'created': '2023-03-17T13:37:42.596Z', - 'modified': '2024-04-11T00:31:21.576Z', - 'object_marking_refs': ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], - 'x_mitre_domains': ['enterprise-attack'], - 'external_references': [{ - 'source_name': 'mitre-attack', - 'url': 'https://attack.mitre.org/campaigns/C0022', - 'external_id': 'C0022' - }, - { - 'source_name': 'Operation Interception', - 'description': '(Citation: ESET Lazarus Jun 2020)' - }, - { - 'source_name': 'Operation North Star', - 'description': '(Citation: McAfee Lazarus Jul 2020)(Citation: McAfee Lazarus Nov 2020)' - }, - { - 'source_name': 'McAfee Lazarus Nov 2020', - 'description': 'Beek, C. (2020, November 5). Operation North Star: Behind The Scenes. Retrieved December 20, 2021.', - 'url': 'https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-behind-the-scenes/' - }, - { - 'source_name': 'ESET Lazarus Jun 2020', - 'description': 'Breitenbacher, D and Osis, K. (2020, June 17). OPERATION IN(TER)CEPTION: Targeted Attacks Against European Aerospace and Military Companies. Retrieved December 20, 2021.', - 'url': 'https://www.welivesecurity.com/wp-content/uploads/2020/06/ESET_Operation_Interception.pdf' - }, - { - 'source_name': 'McAfee Lazarus Jul 2020', - 'description': 'Cashman, M. (2020, July 29). Operation North Star Campaign. Retrieved December 20, 2021.', - 'url': 'https://www.mcafee.com/blogs/other-blogs/mcafee-labs/operation-north-star-a-job-offer-thats-too-good-to-be-true/?hilite=%27Operation%27%2C%27North%27%2C%27Star%27' - }, - { - 'source_name': 'ClearSky Lazarus Aug 2020', - 'description': "ClearSky Research Team. (2020, August 13). Operation 'Dream Job' Widespread North Korean Espionage Campaign. Retrieved December 20, 2021.", - 'url': 'https://www.clearskysec.com/wp-content/uploads/2020/08/Dream-Job-Campaign.pdf' - }, - { - 'source_name': 'The Hacker News Lazarus Aug 2022', - 'description': 'Lakshmanan, R. (2022, August 17). North Korea Hackers Spotted Targeting Job Seekers with macOS Malware. Retrieved April 10, 2023.', - 'url': 'https://thehackernews.com/2022/08/north-korea-hackers-spotted-targeting.html' - }], - 'x_mitre_modified_by_ref': 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - 'x_mitre_deprecated': false, - 'revoked': false, - 'aliases': ['Operation Dream Job', - 'Operation North Star', - 'Operation Interception'], - 'first_seen': '2019-09-01T04:00:00.000Z', - 'last_seen': '2020-08-01T04:00:00.000Z', - 'x_mitre_first_seen_citation': '(Citation: ESET Lazarus Jun 2020)', - 'x_mitre_last_seen_citation': '(Citation: ClearSky Lazarus Aug 2020)' -} - -console.log("\nExample 7 - Parsing the provided example campaign:"); -const e7 = campaignSchema.safeParse(exampleOfRealCampaign); -console.log("Successfully parsed:", e7.success); - -// Successfully parsed: true - -/** ************************************************************************************************* */ -// Example 8: Campaign with unknown property -/** ************************************************************************************************* */ -const campaignWithUnknownProperty = { - ...exampleOfRealCampaign, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a campaign with an unknown property (foo: 'bar'):"); -try { - const parsedCampaign = campaignSchema.parse(campaignWithUnknownProperty); - console.log("Parsed successfully. Campaign name:", parsedCampaign.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Unrecognized key: "foo" - -/** ************************************************************************************************* */ -// Example 9: Campaign with multiple valid citations -/** ************************************************************************************************* */ -const campaignWithMultipleCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: McAfee Lazarus Jul 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: The Hacker News Lazarus Aug 2022)" -}; - -console.log("\nExample 9 - Campaign with multiple valid citations:"); -const e9 = campaignSchema.safeParse(campaignWithMultipleCitations); -console.log("Successfully parsed:", e9.success); - -// Successfully parsed: true - -/** ************************************************************************************************* */ -// Example 10: Campaign with invalid multiple citations (missing parentheses) -/** ************************************************************************************************* */ -const campaignWithInvalidMultipleCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020) (Citation: McAfee Lazarus Jul 2020)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: Invalid Citation)" -}; - -console.log("\nExample 10 - Campaign with invalid multiple citations:"); -try { - campaignSchema.parse(campaignWithInvalidMultipleCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Must be one or more citations in the form '(Citation: [citation name])' without any separators -// โ†’ at x_mitre_first_seen_citation - -/** ************************************************************************************************* */ -// Example 11: Campaign with citation not in external_references -/** ************************************************************************************************* */ -const campaignWithNonExistentCitation = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: Non-existent Source)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("\nExample 11 - Campaign with citation not in external_references:"); -try { - campaignSchema.parse(campaignWithNonExistentCitation); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Citation Non-existent Source not found in external_references. -// โ†’ at x_mitre_first_seen_citation[1] - -/** ************************************************************************************************* */ -// Example 12: Campaign with mixed valid and invalid citations -/** ************************************************************************************************* */ -const campaignWithMixedCitations = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "(Citation: ESET Lazarus Jun 2020)(Citation: Invalid Citation)", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)(Citation: The Hacker News Lazarus Aug 2022)" -}; - -console.log("\nExample 12 - Campaign with mixed valid and invalid citations:"); -try { - campaignSchema.parse(campaignWithMixedCitations); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Citation Invalid Citation not found in external_references. -// โ†’ at x_mitre_first_seen_citation[1] - -/** ************************************************************************************************* */ -// Example 13: Campaign with empty citation string -/** ************************************************************************************************* */ -const campaignWithEmptyCitation = { - ...exampleOfRealCampaign, - x_mitre_first_seen_citation: "", - x_mitre_last_seen_citation: "(Citation: ClearSky Lazarus Aug 2020)" -}; - -console.log("\nExample 13 - Campaign with empty citation string:"); -try { - campaignSchema.parse(campaignWithEmptyCitation); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} - -// โœ– Must be one or more citations in the form '(Citation: [citation name])' without any separators -// โ†’ at x_mitre_first_seen_citation \ No newline at end of file diff --git a/examples/sdo/collection.example.ts b/examples/sdo/collection.example.ts deleted file mode 100644 index 29d63c99..00000000 --- a/examples/sdo/collection.example.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { z } from "zod/v4"; -import { collectionSchema } from "../../src/schemas/sdo/collection.schema.js"; - -/****************************************************************************************************/ -// Example 1: Valid Collection -/****************************************************************************************************/ -const validCollection = { - "id": "x-mitre-collection--1f5f1533-f617-4ca8-9ab4-6a02367fa019", - "type": "x-mitre-collection", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "name": "Enterprise ATT&CK", - "x_mitre_version": "6.2", - "description": "Version 6.2 of the Enterprise ATT&CK dataset", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2018-10-17T00:14:20.652Z", - "modified": "2019-10-11T19:30:42.406Z", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_contents": [ - { - "object_ref": "attack-pattern--01a5a209-b94c-450b-b7f9-946497d91055", - "object_modified": "2019-07-17T20:04:40.297Z" - }, - { - "object_ref": "attack-pattern--0259baeb-9f63-4c69-bf10-eb038c390688", - "object_modified": "2019-06-18T13:58:28.377Z" - }, - { - "object_ref": "relationship--0024d82d-97ea-4dc5-81a1-8738862e1f3b", - "object_modified": "2019-04-24T23:59:16.298Z" - }, - { - "object_ref": "intrusion-set--090242d7-73fc-4738-af68-20162f7a5aae", - "object_modified": "2019-03-22T14:21:19.419Z" - }, - { - "object_ref": "malware--069af411-9b24-4e85-b26c-623d035bbe84", - "object_modified": "2019-04-22T22:40:40.953Z" - }, - { - "object_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_modified": "2017-06-01T00:00:00.000Z" - }, - { - "object_ref": "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - "object_modified": "2017-06-01T00:00:00.000Z" - } - ] -} - -console.log("\nExample 1: Valid Collection:"); -console.log(`SUCCESS ${collectionSchema.parse(validCollection).name}`) - -/****************************************************************************************************/ -// Example 2: Invalid Collection (missing required fields) -/****************************************************************************************************/ -const invalidCollectionMissingFields = { - "id": "x-mitre-collection--1f5f1533-f617-4ca8-9ab4-6a02367fa019", - "type": "x-mitre-collection", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_version": "6.2", - "description": "Version 6.2 of the Enterprise ATT&CK dataset", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2018-10-17T00:14:20.652Z", - "modified": "2019-10-11T19:30:42.406Z", -} -console.log("\nExample 2: Invalid Collection (missing required fields):"); -const e2 = collectionSchema.safeParse(invalidCollectionMissingFields); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 3: Collection with invalid type -/****************************************************************************************************/ -const collectionWithInvalidType = { - ...validCollection, - type: "invalid-type" -}; - -console.log("\nExample 3: Collection with invalid type:"); -const e3 = collectionSchema.safeParse(collectionWithInvalidType); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 4: Collection with optional fields -/****************************************************************************************************/ -const collectionWithOptionalFields = { - ...validCollection, - description: "Version 6.2 of the Enterprise ATT&CK dataset" -}; - -console.log("\nExample 4: Collection with optional fields:"); -console.log(collectionSchema.parse(collectionWithOptionalFields).description); - -/****************************************************************************************************/ -// Example 5: Collection with invalid object version references -/****************************************************************************************************/ -const collectionWithInvalidContents = { - ...validCollection, - x_mitre_contents: [ - { - object_ref: "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - }, - { - object_modified: "2024-02-02T19:04:35.389Z" - }, - { - object_ref: "invalid-id", - object_modified: "2024-02-02T19:04:35.389Z" - } - ] -}; - -console.log("\nExample 5: Collection with invalid object version references:"); -const e5 = collectionSchema.safeParse(collectionWithInvalidContents); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Collection with unknown property -/** ************************************************************************************************* */ -const collectionWithUnknownProperty = { - ...validCollection, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a collection with an unknown property (foo: 'bar'):"); -const e6 = collectionSchema.safeParse(collectionWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Collection name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/data-component.example.ts b/examples/sdo/data-component.example.ts deleted file mode 100644 index 368c118f..00000000 --- a/examples/sdo/data-component.example.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { z } from "zod/v4"; -import { dataComponentSchema } from "../../src/schemas/sdo/data-component.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Data Component -/** ************************************************************************************************* */ -const validDataComponent = { - modified: "2022-10-20T20:18:06.745Z", - name: "Network Connection Creation", - description: - "Initial construction of a network connection, such as capturing socket information with a source/destination IP and port(s) (ex: Windows EID 5156, Sysmon EID 3, or Zeek conn.log)", - x_mitre_data_source_ref: - "x-mitre-data-source--c000cd5c-bbb3-4606-af6f-6c6d9de0bbe3", - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--181a9f8c-c780-4f1f-91a8-edb770e904ba", - created: "2021-10-20T15:05:19.274Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("Example 1 - Valid Data Component:"); -console.log(dataComponentSchema.parse(validDataComponent)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Data Component (missing required fields) -/** ************************************************************************************************* */ -const invalidDataComponent = { - modified: "2022-10-20T20:18:06.745Z", - name: "Network Connection Creation", - x_mitre_data_source_ref: - "x-mitre-data-source--c000cd5c-bbb3-4606-af6f-6c6d9de0bbe3", - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--181a9f8c-c780-4f1f-91a8-edb770e904ba", - created: "2021-10-20T15:05:19.274Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 2 - Invalid Data Component (missing required fields):"); -const e2 = dataComponentSchema.safeParse(invalidDataComponent); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Data Component with optional fields -/** ************************************************************************************************* */ -const dataComponentWithOptionalFields = { - ...validDataComponent, - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Data Component with optional fields:"); -console.log(dataComponentSchema.parse(dataComponentWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Data Component with invalid type -/** ************************************************************************************************* */ -const dataComponentWithInvalidType = { - ...validDataComponent, - type: "invalid-type", -}; - -console.log("\nExample 4 - Data Component with invalid type:"); -const e4 = dataComponentSchema.safeParse(dataComponentWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Data Component with invalid dates -/** ************************************************************************************************* */ -const dataComponentWithInvalidDates = { - ...validDataComponent, - created: "2019-09-01", - modified: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Data Component with invalid dates:"); -const e5 = dataComponentSchema.safeParse(dataComponentWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Data Component -/** ************************************************************************************************* */ -const exampleOfRealDataComponent = { - modified: "2022-10-07T16:15:56.932Z", - name: "Process Creation", - description: - "The initial construction of an executable managed by the OS, that may involve one or more tasks or threads. (e.g. Win EID 4688, Sysmon EID 1, cmd.exe > net use, etc.)", - x_mitre_data_source_ref: - "x-mitre-data-source--e8b8ede7-337b-4c0c-8c32-5c7872c1ee22", - x_mitre_deprecated: false, - x_mitre_version: "1.1", - type: "x-mitre-data-component", - id: "x-mitre-data-component--3d20385b-24ef-40e1-9f56-f39750379077", - created: "2021-10-20T15:05:19.272Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 6 - Parsing the provided example data component:"); -const e6 = dataComponentSchema.safeParse(exampleOfRealDataComponent); -if (e6.success) { - console.log("Parsed successfully. Data Component name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: DataComponent with unknown property -/** ************************************************************************************************* */ -const dataComponentWithUnknownProperty = { - ...exampleOfRealDataComponent, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a dataComponent with an unknown property (foo: 'bar'):"); -const e7 = dataComponentSchema.safeParse(dataComponentWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. DataComponent name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/data-source.example.ts b/examples/sdo/data-source.example.ts deleted file mode 100644 index df5a072f..00000000 --- a/examples/sdo/data-source.example.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { z } from "zod/v4"; -import { - stixCreatedByRefSchema, - stixCreatedTimestampSchema, - stixModifiedTimestampSchema, -} from "../../src/schemas/common/index.js"; -import { dataSourceSchema } from "../../src/schemas/sdo/data-source.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Data Source -/** ************************************************************************************************* */ -const validDataSource = { - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Test Data Source", - x_mitre_version: "1.2", - description: "Test data source description", - created_by_ref: stixCreatedByRefSchema.parse( - "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" - ), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/datasources/DS0014", - external_id: "DS0014" - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_collection_layers: ["Host"], -}; - -console.log("Example 1 - Valid Data Source:"); -console.log(dataSourceSchema.parse(validDataSource)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Data Source (missing required fields) -/** ************************************************************************************************* */ -const invalidDataSource = { - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - spec_version: "2.1", - x_mitre_attack_spec_version: "3.2.0", - name: "Test Data Source", - x_mitre_version: "1.2", - created_by_ref: stixCreatedByRefSchema.parse( - "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" - ), - created: stixCreatedTimestampSchema.parse("2023-03-17T13:37:42.596Z"), - modified: stixModifiedTimestampSchema.parse("2024-04-11T00:31:21.576Z"), - x_mitre_platforms: ["Windows", "Linux"], - x_mitre_domains: ["enterprise-attack"], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_contributors: ["Test Contributer"], - x_mitre_deprecated: false, - revoked: false, - x_mitre_collection_layers: ["Host"], -}; - -console.log("\nExample 2 - Invalid Data Source (missing required fields):"); -const e2 = dataSourceSchema.safeParse(invalidDataSource); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Data Source with optional fields -/** ************************************************************************************************* */ -const dataSourceWithOptionalFields = { - ...validDataSource, - x_mitre_platforms: ['Windows', 'Linux'], - x_mitre_contributors: ["John Doe", "Jane Smith"], - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Data Source with optional fields:"); -console.log(dataSourceSchema.parse(dataSourceWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Data Source with invalid type -/** ************************************************************************************************* */ -const dataSourceWithInvalidType = { - ...validDataSource, - type: "invalid-type", -}; - -console.log("\nExample 4 - Data Source with invalid type:"); -const e4 = dataSourceSchema.safeParse(dataSourceWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Data Source with invalid dates -/** ************************************************************************************************* */ -const dataSourceWithInvalidDates = { - ...validDataSource, - created: "2019-09-01", - modified: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Data source with invalid dates:"); -const e5 = dataSourceSchema.safeParse(dataSourceWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Data Source -/** ************************************************************************************************* */ -const exampleOfRealDataSource = { - modified: "2023-04-20T18:38:40.409Z", - name: "Sensor Health", - description: - "Information from host telemetry providing insights about system status, errors, or other notable functional activity", - x_mitre_platforms: ["Linux", "Windows", "macOS", "Android", "iOS"], - x_mitre_deprecated: false, - x_mitre_domains: ["enterprise-attack", "mobile-attack"], - x_mitre_version: "1.1", - x_mitre_contributors: ["Center for Threat-Informed Defense (CTID)"], - x_mitre_collection_layers: ["Host"], - type: "x-mitre-data-source", - id: "x-mitre-data-source--4523e7f3-8de2-4078-96f8-1227eb537159", - created: "2021-10-20T15:05:19.272Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/datasources/DS0013", - external_id: "DS0013", - }, - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "3.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", -}; - -console.log("\nExample 6 - Parsing the provided example data source:"); -const e6 = dataSourceSchema.safeParse(exampleOfRealDataSource); -if (e6.success) { - console.log("Parsed successfully. Data Source name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: DataSource with unknown property -/** ************************************************************************************************* */ -const dataSourceWithUnknownProperty = { - ...exampleOfRealDataSource, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a dataSource with an unknown property (foo: 'bar'):"); -const e7 = dataSourceSchema.safeParse(dataSourceWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. DataSource name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/detection-strategy.example.ts b/examples/sdo/detection-strategy.example.ts deleted file mode 100644 index 58fc8354..00000000 --- a/examples/sdo/detection-strategy.example.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import { detectionStrategySchema } from "../../src/schemas/sdo/detection-strategy.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Detection Strategy -/*************************************************************************************************** */ -const validDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43d", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Credential Dumping via Sensitive Memory and Registry Access Correlation", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0001", - "url": "https://attack.mitre.org/detection-strategies/DET0001" - } - ], - "x_mitre_contributors": [ - "Security Research Team", - "John Smith" - ], - "x_mitre_analytic_refs": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}` - ] -}; - -console.log("\nExample 1 - Valid Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Detection Strategy (ATT&CK ID does not match format DET####) -/*************************************************************************************************** */ -const invalidDetectionStrategyID = { - ...validDetectionStrategy, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/detection-strategies/DET0001", - "external_id": "D001" - } - ], -}; - -console.log("\nExample 2 - Invalid Detection Strategy (ATT&CK ID does not match format DET####):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: โœ– The first external_reference must match the ATT&CK ID format DET####. -// โ†’ at external_references[0].external_id - -/*************************************************************************************************** */ -// Example 3: Valid Detection Strategy with Multiple Contributors -/*************************************************************************************************** */ -const validDetectionStrategyWithContributors = { - ...validDetectionStrategy, - "name": "PowerShell Obfuscation Detection", - "x_mitre_contributors": [ - "John Smith", - "Jane Doe", - "Security Research Team", - "Advanced Threat Analytics Lab" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0002", - "url": "https://attack.mitre.org/detection-strategies/DET0002" - } - ], - "x_mitre_analytic_refs": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 3 - Valid Detection Strategy with Multiple Contributors:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategyWithContributors).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Detection Strategy (missing required fields) -/*************************************************************************************************** */ -const invalidDetectionStrategyMissingFields = { - "id": "x-mitre-detection-strategy--550e8400-e29b-41d4-a716-446655440001", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - // Missing name - // Missing created_by_ref (required by schema) - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // Missing object_marking_refs (required by schema) - // Missing x_mitre_domains - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0003", - "url": "https://attack.mitre.org/detection-strategies/DET0003" - } - ], - // Missing x_mitre_contributors (required) - // Missing x_mitre_analytic_refs -}; - -console.log("\nExample 4 - Invalid Detection Strategy (missing required fields):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyMissingFields); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: โœ– Invalid input: expected nonoptional, received undefined -// โ†’ at created_by_ref -// โœ– Invalid input: expected nonoptional, received undefined -// โ†’ at object_marking_refs -// โœ– Invalid input: expected string, received undefined -// โ†’ at name -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_contributors -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_analytic_refs -// โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_domains - -/*************************************************************************************************** */ -// Example 5: Invalid Detection Strategy with invalid type -/*************************************************************************************************** */ -const detectionStrategyWithInvalidType = { - ...validDetectionStrategy, - "type": 'invalid-type' -} - -console.log("\nExample 5 - Detection Strategy with invalid type:"); -try { - detectionStrategySchema.parse(detectionStrategyWithInvalidType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log(z.prettifyError(error)); - } -} -// โœ– Invalid input: expected "x-mitre-detection-strategy" -// โ†’ at type - -/*************************************************************************************************** */ -// Example 6: Invalid Detection Strategy (empty analytics array) -/*************************************************************************************************** */ -const invalidDetectionStrategyEmptyAnalytics = { - ...validDetectionStrategy, - "x_mitre_analytic_refs": [] -}; - -console.log("\nExample 6 - Invalid Detection Strategy (empty analytics array):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyEmptyAnalytics); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: โœ– Too small: expected array to have >=1 items -// โ†’ at x_mitre_analytic_refs - -/*************************************************************************************************** */ -// Example 7: Invalid Detection Strategy (wrong analytic ID format) -/*************************************************************************************************** */ -const invalidDetectionStrategyAnalyticID = { - ...validDetectionStrategy, - "x_mitre_analytic_refs": [ - "invalid-analytic-id", - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 7 - Invalid Detection Strategy (wrong analytic ID format):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyAnalyticID); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: โœ– Invalid STIX Identifier: must comply with format 'type--UUIDv4' -// โ†’ at x_mitre_analytic_refs[0] -// โœ– Invalid STIX Identifier for STIX object: contains invalid STIX type 'invalid-analytic-id' -// โ†’ at x_mitre_analytic_refs[0] -// โœ– Invalid STIX Identifier for STIX object: contains invalid UUIDv4 format -// โ†’ at x_mitre_analytic_refs[0] -// โœ– Invalid STIX Identifier: must start with 'x-mitre-analytic--' -// โ†’ at x_mitre_analytic_refs[0] - -/*************************************************************************************************** */ -// Example 8: Invalid Detection Strategy (wrong STIX type in analytic ID) -/*************************************************************************************************** */ -const invalidDetectionStrategyWrongStixType = { - ...validDetectionStrategy, - "x_mitre_analytic_refs": [ - "x-mitre-detection-strategy--6ba7b810-9dad-11d1-80b4-00c04fd430c8" - ] -}; - -console.log("\nExample 8 - Invalid Detection Strategy (wrong STIX type in analytic ID):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyWrongStixType); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: โœ– Invalid STIX Identifier: must start with 'x-mitre-analytic--' -// โ†’ at x_mitre_analytic_refs[0] - -/*************************************************************************************************** */ -// Example 9: Valid Detection Strategy with Mobile Domain -/*************************************************************************************************** */ -const validMobileDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c8d9e10-f3ab-22e5-b827-556766550001", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Mobile Application Behavior Analysis", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.2", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": [ - "mobile-attack" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0015", - "url": "https://attack.mitre.org/detection-strategies/DET0015" - } - ], - "x_mitre_contributors": [ - "Mobile Security Research Lab", - "Dr. Sarah Johnson" - ], - "x_mitre_analytic_refs": [ - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 9 - Valid Mobile Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validMobileDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 10: Valid Multi-Domain Detection Strategy -/*************************************************************************************************** */ -const validMultiDomainDetectionStrategy = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43e", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Cross-Platform Command Execution Detection", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": [ - "enterprise-attack", - "ics-attack" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0025", - "url": "https://attack.mitre.org/detection-strategies/DET0025" - } - ], - "x_mitre_contributors": [ - "Cross-Platform Security Team", - "Industrial Control Systems Lab" - ], - "x_mitre_analytic_refs": [ - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 10 - Valid Multi-Domain Detection Strategy:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validMultiDomainDetectionStrategy).name}`) - -/*************************************************************************************************** */ -// Example 11: Invalid Detection Strategy (invalid domain) -/*************************************************************************************************** */ -const invalidDetectionStrategyDomain = { - ...validDetectionStrategy, - "x_mitre_domains": [ - "invalid-domain" - ] -}; - -console.log("\nExample 11 - Invalid Detection Strategy (invalid domain):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyDomain); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: โœ– Invalid option: expected one of "enterprise-attack"|"mobile-attack"|"ics-attack" -// โ†’ at x_mitre_domains[0] - -/*************************************************************************************************** */ -// Example 12: Invalid Detection Strategy (missing contributors) -/*************************************************************************************************** */ -const invalidDetectionStrategyMissingContributors = { - ...validDetectionStrategy, - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0030", - "url": "https://attack.mitre.org/detection-strategies/DET0030" - } - ] -}; -// Remove required contributors field -delete invalidDetectionStrategyMissingContributors.x_mitre_contributors; - -console.log("\nExample 12 - Invalid Detection Strategy (missing contributors):"); -try { - detectionStrategySchema.parse(invalidDetectionStrategyMissingContributors); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation error:", z.prettifyError(error)); - } -} -// Validation error: โœ– Invalid input: expected array, received undefined -// โ†’ at x_mitre_contributors - -/*************************************************************************************************** */ -// Example 13: Valid Detection Strategy with Special Characters -/*************************************************************************************************** */ -const validDetectionStrategySpecialChars = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a43f", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Advanced Persistent Threat (APT) Detection Strategy v2.0", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0099", - "url": "https://attack.mitre.org/detection-strategies/DET0099" - } - ], - "x_mitre_contributors": [ - "Dr. John Smith, PhD", - "Jane Doe (Lead Analyst)", - "Security Team Alpha-1", - "Josรฉ Garcรญa-Rodriguez", - "ๆŽๅฐๆ˜Ž", - "user@security-domain.com" - ], - "x_mitre_analytic_refs": [ - `x-mitre-analytic--${uuidv4()}`, - ] -}; - -console.log("\nExample 13 - Valid Detection Strategy with Special Characters:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategySpecialChars).name}`) - -/*************************************************************************************************** */ -// Example 14: Detection Strategy with unknown property -/*************************************************************************************************** */ -const detectionStrategyWithUnknownProperty = { - ...validDetectionStrategy, - foo: 'bar' -} - -console.log("\nExample 14 - Parsing a detection strategy with an unknown property (foo: 'bar'):"); -try { - const parsedDetectionStrategy = detectionStrategySchema.parse(detectionStrategyWithUnknownProperty); - console.log("Parsed successfully. Detection Strategy name:", parsedDetectionStrategy.name); -} catch (error) { - if (error instanceof z.core.$ZodError) { - console.log("Validation errors:", z.prettifyError(error)); - } -} -// Validation errors: โœ– Unrecognized key: "foo" - -/*************************************************************************************************** */ -// Example 15: Valid Detection Strategy with Large Number of Analytics -/*************************************************************************************************** */ -const validDetectionStrategyManyAnalytics = { - "id": "x-mitre-detection-strategy--7c93fd1a-1e6b-4b4e-be42-0d843216a440", - "type": "x-mitre-detection-strategy", - "spec_version": "2.1", - "created": "2025-06-03T15:32:27Z", - "modified": "2025-06-03T15:32:27Z", - "name": "Comprehensive Threat Detection Suite", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0", - "x_mitre_attack_spec_version": "4.0.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_domains": ["enterprise-attack"], - "external_references": [ - { - "source_name": "mitre-attack", - "external_id": "DET0100", - "url": "https://attack.mitre.org/detection-strategies/DET0100" - } - ], - "x_mitre_contributors": [ - "Comprehensive Detection Team" - ], - "x_mitre_analytic_refs": Array.from({ length: 20 }, (_, i) => - `x-mitre-analytic--${uuidv4()}`, - ) -}; - -console.log("\nExample 15 - Valid Detection Strategy with Many Analytics:"); -console.log(`SUCCESS ${detectionStrategySchema.parse(validDetectionStrategyManyAnalytics).name} (${validDetectionStrategyManyAnalytics.x_mitre_analytic_refs.length} analytics)`); \ No newline at end of file diff --git a/examples/sdo/group.example.ts b/examples/sdo/group.example.ts deleted file mode 100644 index 1d2cb190..00000000 --- a/examples/sdo/group.example.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { z } from "zod/v4"; -import { v4 as uuidv4 } from 'uuid'; -import type { StixCreatedTimestamp, StixModifiedTimestamp } from "../../src/schemas/common/index.js"; -import { groupSchema } from "../../src/schemas/sdo/group.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Group -/** ************************************************************************************************* */ -const validGroup = { - id: `intrusion-set--${uuidv4()}`, - type: "intrusion-set", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Test Name", - x_mitre_version: "1.0", - created: "2017-06-01T00:00:00.000Z" as StixCreatedTimestamp, - modified: "2017-06-01T00:00:00.000Z" as StixModifiedTimestamp, - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - external_id: "G1000", - url: "https://attack.mitre.org/groups/G1000", - }, - { - source_name: "Dragos", - url: "https://dragos.com/resource/allanite/", - description: "Dragos Allanite Retrieved. 2019/10/27 ", - }, - ], -}; - -console.log("Example 1 - Valid Group:"); -console.log(groupSchema.parse(validGroup)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Group (missing required fields) -/** ************************************************************************************************* */ -const invalidGroup = { - modified: "2024-04-17T16:13:43.697Z", - x_mitre_version: "1.4", - type: "intrusion-set", - id: "intrusion-set--9538b1a4-4120-4e2d-bf59-3b11fcab05a4", - created: "2019-04-16T15:14:38.533Z", - x_mitre_attack_spec_version: "3.2.0", - spec_version: "2.1", -}; - -console.log("\nExample 2 - Invalid Group (missing required fields):"); -const e2 = groupSchema.safeParse(invalidGroup); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Group with optional fields -/** ************************************************************************************************* */ -const groupWithOptionalFields = { - ...validGroup, - description: - "[TEMP.Veles](https://attack.mitre.org/groups/G0088) is a Russia-based threat group that has targeted critical infrastructure. The group has been observed utilizing [TRITON](https://attack.mitre.org/software/S0609), a malware framework designed to manipulate industrial safety systems.(Citation: FireEye TRITON 2019)(Citation: FireEye TEMP.Veles 2018)(Citation: FireEye TEMP.Veles JSON April 2019)", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/groups/G0088", - external_id: "G0088", - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_contributors: ["Dragos Threat Intelligence"], - x_mitre_deprecated: false, - revoked: false, - aliases: ["Test Name", "XENOTIME"], -}; - -console.log("\nExample 3 - Group with optional fields:"); -console.log(groupSchema.parse(groupWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Group with invalid type -/** ************************************************************************************************* */ -const grouptWithInvalidType = { - ...validGroup, - type: "invalid-type", -}; - -console.log("\nExample 4 - Group with invalid type:"); -const e4 = groupSchema.safeParse(grouptWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Group with invalid dates -/** ************************************************************************************************* */ -const groupWithInvalidDates = { - ...validGroup, - first_seen: "2019-09-01", - last_seen: "2020-08-01T04:00:00.000Z", -}; - -console.log("\nExample 5 - Group with invalid dates:"); -const e5 = groupSchema.safeParse(groupWithInvalidDates); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example Group -/** ************************************************************************************************* */ -const exampleOfRealGroup = { - modified: "2024-04-10T18:39:36.997Z", - name: "CyberAv3ngers", - description: - "The [CyberAv3ngers](https://attack.mitre.org/groups/G1027) are a suspected Iranian Government Islamic Revolutionary Guard Corps (IRGC)-affiliated APT group. The [CyberAv3ngers](https://attack.mitre.org/groups/G1027) have been known to be active since at least 2020, with disputed and false claims of critical infrastructure compromises in Israel.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)\n\nIn 2023, the [CyberAv3ngers](https://attack.mitre.org/groups/G1027) engaged in a global targeting and hacking of the Unitronics [Programmable Logic Controller (PLC)](https://attack.mitre.org/assets/A0003) with [Human-Machine Interface (HMI)](https://attack.mitre.org/assets/A0002). This PLC can be found in multiple sectors, including water and wastewater, energy, food and beverage manufacturing, and healthcare. The most notable feature of this attack was the defacement of the devices user interface.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)", - aliases: ["CyberAv3ngers", "Soldiers of Soloman"], - x_mitre_deprecated: false, - x_mitre_version: "1.0", - type: "intrusion-set", - id: "intrusion-set--a07a367a-146c-45a8-a830-d3d337b9befa", - created: "2024-03-25T19:57:07.829Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - revoked: false, - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/groups/G1027", - external_id: "G1027", - }, - { - source_name: "Soldiers of Soloman", - description: - "CyberAv3ngers reportedly has connections to the IRGC-linked group Soldiers of Solomon.(Citation: CISA AA23-335A IRGC-Affiliated December 2023)", - }, - { - source_name: "CISA AA23-335A IRGC-Affiliated December 2023", - description: - "DHS/CISA. (2023, December 1). IRGC-Affiliated Cyber Actors Exploit PLCs in Multiple Sectors, Including U.S. Water and Wastewater Systems Facilities. Retrieved March 25, 2024.", - url: "https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-335a", - }, - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["ics-attack"], - x_mitre_attack_spec_version: "3.2.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", -}; - -console.log("\nExample 6 - Parsing the provided example group:"); -const e6 = groupSchema.safeParse(exampleOfRealGroup); -if (e6.success) { - console.log("Parsed successfully. Group name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: Group with unknown property -/** ************************************************************************************************* */ -const groupWithUnknownProperty = { - ...validGroup, - foo: 'bar' -} - -console.log("\nExample 7 - Parsing a group with an unknown property (foo: 'bar'):"); -const e7 = groupSchema.safeParse(groupWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. Group name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/identity.example.ts b/examples/sdo/identity.example.ts deleted file mode 100644 index 85d37dc0..00000000 --- a/examples/sdo/identity.example.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { z } from "zod/v4" -import { identitySchema } from "../../src/schemas/sdo/identity.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Identity -/** ************************************************************************************************* */ - -const validIdentity = { - type: 'identity', - id: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - created: "2017-06-01T00:00:00.000Z", - modified: "2017-06-01T00:00:00.000Z", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - identity_class: "organization", - name: "The MITRE Corporation", - x_mitre_attack_spec_version: "3.2.0", - spec_version: "2.1" -}; - -console.log("Example 1 - Valid Identity:"); -console.log(identitySchema.parse(validIdentity)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Identity (missing required fields) -/** ************************************************************************************************* */ - -const invalidIdentity = { - type: 'identity', - id: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - created: "2017-06-01T00:00:00.000Z", - modified: "2017-06-01T00:00:00.000Z", - x_mitre_domains:["enterprise-attack"], - name: "The MITRE Corporation", - x_mitre_attack_spec_version: "3.2.0", - x_mitre_version: "1.0", - spec_version: "2.1" -}; - -console.log("Example 2 - Invalid Identity (missing required fields):"); -const e2 = identitySchema.safeParse(invalidIdentity); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Identity with invalid id -/** ************************************************************************************************* */ -const identityWithInvalidId = { - ...validIdentity, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 3 - Identity with invalid id:"); -const e3 = identitySchema.safeParse(identityWithInvalidId); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 4: Identity with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const identityWithStixFields = { - ...validIdentity, - description: "identity object description", - roles: ["administrator"], - sectors: ["non-profit"], - contact_information: "attack@mitre.org" -}; - -console.log("\nExample 4 - Identity with fields in STIX but not in ATT&CK:"); -console.log(identitySchema.parse(identityWithStixFields)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example identity -/** ************************************************************************************************* */ -const exampleOfRealIdentity = { - "type": "identity", - "id": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-06-01T00:00:00.000Z", - "modified": "2017-06-01T00:00:00.000Z", - "name": "The MITRE Corporation", - "identity_class": "organization", - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_version": "1.0", - "spec_version": "2.1", - "x_mitre_domains":["enterprise-attack"], -} - -console.log("\nExample 5 - Parsing the provided example identity:"); -const e5 = identitySchema.safeParse(exampleOfRealIdentity); -if (e5.success) { - console.log(e5.data); - console.log("Parsed successfully. Identity name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Identity with unknown property -/** ************************************************************************************************* */ -const identityWithUnknownProperty = { - ...exampleOfRealIdentity, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a identity with an unknown property (foo: 'bar'):"); -const e6 = identitySchema.safeParse(identityWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Identity name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/malware.example.ts b/examples/sdo/malware.example.ts deleted file mode 100644 index 762debe6..00000000 --- a/examples/sdo/malware.example.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { z } from "zod/v4"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Malware -/** ************************************************************************************************* */ - -const validMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - is_family: false, - x_mitre_version: "1.2" -}; - -console.log("Example 1 - Valid Malware:"); -console.log(malwareSchema.parse(validMalware)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Malware (missing required fields) -/** ************************************************************************************************* */ - -const invalidMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Malware (missing required fields):"); -const e2 = malwareSchema.safeParse(invalidMalware); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Malware with optional fields -/** ************************************************************************************************* */ -const malwareWithOptionalFields = { - ...validMalware, - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_contributors: ["Contributor"], - x_mitre_deprecated: false, - x_mitre_old_attack_id: "MOB-S0123" -}; - -console.log("\nExample 3 - Malware with optional fields:"); -console.log(malwareSchema.parse(malwareWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Malware with invalid type -/** ************************************************************************************************* */ -const malwareWithInvalidType = { - ...validMalware, - type: "invalid-type", -}; - -console.log("\nExample 4 - Malware with invalid type:"); -const e4 = malwareSchema.safeParse(malwareWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Malware with invalid id -/** ************************************************************************************************* */ -const malwareWithInvalidId = { - ...validMalware, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Malware with invalid id:"); -const e5 = malwareSchema.safeParse(malwareWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Malware with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const malwareWithStixFields = { - ...validMalware, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - first_seen: "2015-07-01T00:00:00.000Z", - last_seen: "2016-07-01T00:00:00.000Z", - malware_types: ["remote-access-trojan"], - os_execution_envs: [ - "Windows", - "Linux" - ], - architecture_execution_envs: [ - "x86" - ], - capabilities: [ - "exfiltrates-data", - "accesses-remote-machines" - ], - sample_refs: [ - "file--a3b8b3b2-4d2f-4a2e-9a1b-1c8b3e4e6f5d" - ], - implementation_languages: [ - "python" - ] -}; - -console.log("\nExample 6 - Malware with fields in STIX but not in ATT&CK:"); -console.log(malwareSchema.parse(malwareWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example malware -/** ************************************************************************************************* */ - -const exampleOfRealMalware = { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false -} - -console.log("\nExample 7 - Parsing the provided example malware:"); -const e7 = malwareSchema.safeParse(exampleOfRealMalware); -if (e7.success) { - console.log(e7.data); - console.log("Parsed successfully. Malware name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Malware with unknown property -/** ************************************************************************************************* */ -const malwareWithUnknownProperty = { - ...exampleOfRealMalware, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a malware with an unknown property (foo: 'bar'):"); -const e8 = malwareSchema.safeParse(malwareWithUnknownProperty); -if (e8.success) { - console.log("Parsed successfully. Malware name:", e8.data.name); -} else { - console.log(z.prettifyError(e8.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/matrix.example.ts b/examples/sdo/matrix.example.ts deleted file mode 100644 index c30f4549..00000000 --- a/examples/sdo/matrix.example.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { matrixSchema } from "../../src/schemas/sdo/matrix.schema.js"; -import { z } from "zod/v4"; - -/** ************************************************************************************************* */ -// Example 1: Valid Matrix -/** ************************************************************************************************* */ -const validMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created: "2018-10-17T00:14:20.652Z", - description: - "The full ATT&CK for ICS Matrix includes techniques spanning various ICS assets and can be used to navigate through the knowledge base.", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("Example 1 - Valid Matrix:"); -console.log(matrixSchema.parse(validMatrix)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Matrix (missing required fields) -/** ************************************************************************************************* */ -const invalidMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - created: "2018-10-17T00:14:20.652Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("\nExample 2 - Invalid Matrix (missing required fields):"); -const e2 = matrixSchema.safeParse(invalidMatrix); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Matrix with optional fields -/** ************************************************************************************************* */ -const matrixWithOptionalFields = { - ...validMatrix, - x_mitre_deprecated: false, - revoked: false, -}; - -console.log("\nExample 3 - Matrix with optional fields:"); -console.log(matrixSchema.parse(matrixWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Matrix with invalid type -/** ************************************************************************************************* */ -const matrixWithInvalidType = { - ...validMatrix, - type: "invalid-type", -}; - -console.log("\nExample 4 - Matrix with invalid type:"); -const e4 = matrixSchema.safeParse(matrixWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Matrix -/** ************************************************************************************************* */ -const exampleOfRealMatrix = { - tactic_refs: [ - "x-mitre-tactic--69da72d2-f550-41c5-ab9e-e8255707f28a", - "x-mitre-tactic--93bf9a8e-b14c-4587-b6d5-9efc7c12eb45", - "x-mitre-tactic--78f1d2ae-a579-44c4-8fc5-3e1775c73fac", - "x-mitre-tactic--33752ae7-f875-4f43-bdb6-d8d02d341046", - "x-mitre-tactic--ddf70682-f3ce-479c-a9a4-7eadf9bfead7", - "x-mitre-tactic--696af733-728e-49d7-8261-75fdc590f453", - "x-mitre-tactic--51c25a9e-8615-40c0-8afd-1da578847924", - "x-mitre-tactic--b2a67b1e-913c-46f6-b219-048a90560bb9", - "x-mitre-tactic--97c8ff73-bd14-4b6c-ac32-3d91d2c41e3f", - "x-mitre-tactic--298fe907-7931-4fd2-8131-2814dd493134", - "x-mitre-tactic--ff048b6c-b872-4218-b68c-3735ebd1f024", - "x-mitre-tactic--77542f83-70d0-40c2-8a9d-ad2eb8b00279", - ], - x_mitre_domains: ["ics-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created: "2018-10-17T00:14:20.652Z", - description: - "The full ATT&CK for ICS Matrix includes techniques spanning various ICS assets and can be used to navigate through the knowledge base.", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - external_id: "ics-attack", - url: "https://attack.mitre.org/matrices/ics/", - }, - ], - id: "x-mitre-matrix--575f48f4-8897-4468-897b-48bb364af6c7", - modified: "2022-05-24T14:00:00.188Z", - name: "ATT&CK for ICS", - type: "x-mitre-matrix", - x_mitre_attack_spec_version: "2.1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.0", - spec_version: "2.1", -}; - -console.log("\nExample 5 - Parsing the provided example matrix:"); -const e5 = matrixSchema.safeParse(exampleOfRealMatrix); -if (e5.success) { - console.log("Parsed successfully. Matrix name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Matrix with unknown property -/** ************************************************************************************************* */ -const matrixWithUnknownProperty = { - ...validMatrix, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a matrix with an unknown property (foo: 'bar'):"); -const e6 = matrixSchema.safeParse(matrixWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Matrix name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/mitigation.example.ts b/examples/sdo/mitigation.example.ts deleted file mode 100644 index 7065ed36..00000000 --- a/examples/sdo/mitigation.example.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { z } from "zod/v4"; -import { mitigationSchema } from "../../src/schemas/sdo/mitigation.schema.js"; - -/** ************************************************************************************************* */ -// Example 1: Valid Mitigation -/** ************************************************************************************************* */ -const validMitigation = { - type: "course-of-action", - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Mitigation name", - x_mitre_version: "1.0", - description: "Mitigation description", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2018-10-17T00:14:20.652Z", - modified: "2019-07-25T11:22:19.139Z", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_domains: ["enterprise-attack"], - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/mitigations/M1174", - external_id: "M1174", - }, - { - url: "https://example.com", - description: "Example description.", - source_name: "Example source name", - }, - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", -}; - -console.log("Example 1 - Valid Mitigation:"); -console.log(mitigationSchema.parse(validMitigation)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Mitigation (missing required fields) -/** ************************************************************************************************* */ -const invalidMitigation = { - type: "course-of-action", - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", - name: "Mitigation name", - x_mitre_version: "1.0", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2018-10-17T00:14:20.652Z", - modified: "2019-07-25T11:22:19.139Z", - x_mitre_domains: ["enterprise-attack"], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", -}; - -console.log("\nExample 2 - Invalid Mitigation (missing required fields):"); -const e2 = mitigationSchema.safeParse(invalidMitigation); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Mitigation with optional fields -/** ************************************************************************************************* */ -const mitigationWithOptionalFields = { - ...validMitigation, - x_mitre_deprecated: false, - revoked: false, - labels: [ - "IEC 62443-3-3:2013 - SR 5.1", - "IEC 62443-4-2:2019 - CR 5.1", - "NIST SP 800-53 Rev. 5 - AC-3; SC-7", - ], - x_mitre_old_attack_id: "MOB-M1008", -}; - -console.log("\nExample 3 - Mitigation with optional fields:"); -console.log(mitigationSchema.parse(mitigationWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Mitigation with invalid type -/** ************************************************************************************************* */ -const mitigationWithInvalidType = { - ...validMitigation, - type: "invalid-type", -}; - -console.log("\nExample 4 - Mitigation with invalid type:"); -const e4 = mitigationSchema.safeParse(mitigationWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Mitigation -/** ************************************************************************************************* */ -const exampleOfRealMitigation = { - x_mitre_domains: ["enterprise-attack"], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - id: "course-of-action--00d7d21b-69d6-4797-88a2-c86f3fc97651", - type: "course-of-action", - created: "2018-10-17T00:14:20.652Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/mitigations/T1174", - external_id: "T1174", - }, - { - url: "https://msdn.microsoft.com/library/windows/desktop/ms721766.aspx", - description: - "Microsoft. (n.d.). Installing and Registering a Password Filter DLL. Retrieved November 21, 2017.", - source_name: "Microsoft Install Password Filter n.d", - }, - ], - modified: "2019-07-25T11:22:19.139Z", - name: "Password Filter DLL Mitigation", - description: - "Ensure only valid password filters are registered. Filter DLLs must be present in Windows installation directory (C:\\Windows\\System32\\ by default) of a domain controller and/or local computer with a corresponding entry in HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\Notification Packages. (Citation: Microsoft Install Password Filter n.d)", - x_mitre_deprecated: true, - x_mitre_version: "1.0", - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - spec_version: "2.1", - x_mitre_attack_spec_version: "2.1.0", -}; - -console.log("\nExample 5 - Parsing the provided example mitigation:"); -const e5 = mitigationSchema.safeParse(exampleOfRealMitigation); -if (e5.success) { - console.log("Parsed successfully. Mitigation name:", e5.data.name); -} else { - console.log(z.prettifyError(e5.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 6: Mitigation with unknown property -/** ************************************************************************************************* */ -const mitigationWithUnknownProperty = { - ...validMitigation, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a mitigation with an unknown property (foo: 'bar'):"); -const e6 = mitigationSchema.safeParse(mitigationWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Mitigation name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/software.example.ts b/examples/sdo/software.example.ts deleted file mode 100644 index 659cba6c..00000000 --- a/examples/sdo/software.example.ts +++ /dev/null @@ -1,490 +0,0 @@ -import { z } from "zod/v4"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; -import { toolSchema } from "../../src/schemas/sdo/tool.schema.js"; - -// Malware Examples - -console.log("****************************************************************************************************") -console.log("Malware Examples") -console.log("****************************************************************************************************") -/** ************************************************************************************************* */ -// Example 1: Valid Malware -/** ************************************************************************************************* */ - -const validMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - is_family: false, - x_mitre_version: "1.2" -}; - -console.log("Example 1 - Valid Malware:"); -console.log(malwareSchema.parse(validMalware)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Malware (missing required fields) -/** ************************************************************************************************* */ - -const invalidMalware = { - type: 'malware', - id: 'malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2017-05-31T21:32:29.203Z", - modified: "2021-02-09T13:58:23.806Z", - name: "HAMMERTOSS", - description: "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0037", - external_id: "S0037" - }, - { - source_name: "FireEye APT29", - description: "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - url: "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Malware (missing required fields):"); -const e2 = malwareSchema.safeParse(invalidMalware); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Malware with optional fields -/** ************************************************************************************************* */ -const malwareWithOptionalFields = { - ...validMalware, - x_mitre_aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - x_mitre_platforms: [ - "Windows" - ], - x_mitre_contributors: ["Contributor"], - x_mitre_deprecated: false, - x_mitre_old_attack_id: "MOB-S0123" -}; - -console.log("\nExample 3 - Malware with optional fields:"); -console.log(malwareSchema.parse(malwareWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Malware with invalid type -/** ************************************************************************************************* */ -const malwareWithInvalidType = { - ...validMalware, - type: "invalid-type", -}; - -console.log("\nExample 4 - Malware with invalid type:"); -const e4 = malwareSchema.safeParse(malwareWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Malware with invalid id -/** ************************************************************************************************* */ -const malwareWithInvalidId = { - ...validMalware, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Malware with invalid id:"); -const e5 = malwareSchema.safeParse(malwareWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Malware with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const malwareWithStixFields = { - ...validMalware, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - first_seen: "2015-07-01T00:00:00.000Z", - last_seen: "2016-07-01T00:00:00.000Z", - malware_types: ["remote-access-trojan"], - os_execution_envs: [ - "Windows", - "Linux" - ], - architecture_execution_envs: [ - "x86" - ], - capabilities: [ - "exfiltrates-data", - "accesses-remote-machines" - ], - sample_refs: [ - "file--a3b8b3b2-4d2f-4a2e-9a1b-1c8b3e4e6f5d" - ], - implementation_languages: [ - "python" - ] -}; - -console.log("\nExample 6 - Malware with fields in STIX but not in ATT&CK:"); -console.log(malwareSchema.parse(malwareWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example malware -/** ************************************************************************************************* */ - -const exampleOfRealMalware = { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false -} - -console.log("\nExample 7 - Parsing the provided example malware:"); -const e7 = malwareSchema.safeParse(exampleOfRealMalware); -if (e7.success) { - console.log(e7.data); - console.log("Parsed successfully. Malware name:", e7.data.name); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Malware with unknown property -/** ************************************************************************************************* */ -const malwareWithUnknownProperty = { - ...exampleOfRealMalware, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a malware with an unknown property (foo: 'bar'):"); -const e8 = malwareSchema.safeParse(malwareWithUnknownProperty); -if (e8.success) { - console.log("Parsed successfully. Malware name:", e8.data.name); -} else { - console.log(z.prettifyError(e8.error as z.core.$ZodError)); -} - -// Tool Examples - -console.log("****************************************************************************************************") -console.log("Tool Examples") -console.log("****************************************************************************************************") - -/** ************************************************************************************************* */ -// Example 1: Valid Tool -/** ************************************************************************************************* */ - -const validTool = { - type: 'tool', - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: "2021-07-30T15:43:17.770Z", - modified: "2024-04-11T00:06:01.264Z", - name: "Sliver", - description: '[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)', - external_references: [ - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0049", - external_id: "S0049" - }, - { - source_name: "F-Secure The Dukes", - description: "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - url: "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_version: "1.2", - x_mitre_attack_spec_version: "3.2.0" -}; - -console.log("Example 1 - Valid Tool:"); -console.log(toolSchema.parse(validTool)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Tool (missing required fields) -/** ************************************************************************************************* */ - -const invalidTool = { - type: 'tool', - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', - spec_version: '2.1', - created: "2021-07-30T15:43:17.770Z", - modified: "2024-04-11T00:06:01.264Z", - name: "Sliver", - description: '[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)', - x_mitre_aliases: [ - "Sliver" - ], - x_mitre_contributors: [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - x_mitre_deprecated: false, - x_mitre_domains: [ - "enterprise-attack" - ], - x_mitre_modified_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - x_mitre_platforms: [ - "Windows", - "Linux", - "macOS" - ], - x_mitre_version: "1.2" -}; - -console.log("Example 2 - Invalid Tool (missing required fields):"); -const et2 = toolSchema.safeParse(invalidTool); -console.log(z.prettifyError(et2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Tool with optional fields -/** ************************************************************************************************* */ -const toolWithOptionalFields = { - ...validTool, - x_mitre_contributors: [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - x_mitre_aliases: [ - "Sliver" - ], - x_mitre_deprecated: false, - x_mitre_platforms: [ - "Windows", - "Linux", - "macOS" - ] -}; - -console.log("\nExample 3 - Tool with optional fields:"); -console.log(toolSchema.parse(toolWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 4: Tool with invalid type -/** ************************************************************************************************* */ -const toolWithInvalidType = { - ...validTool, - type: "invalid-type", -}; - -console.log("\nExample 4 - Tool with invalid type:"); -const et4 = toolSchema.safeParse(toolWithInvalidType); -console.log(z.prettifyError(et4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Tool with invalid id -/** ************************************************************************************************* */ -const toolWithInvalidId = { - ...validTool, - id: 'malware--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Tool with invalid id:"); -const et5 = toolSchema.safeParse(toolWithInvalidId); -console.log(z.prettifyError(et5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Tool with fields in STIX but not in ATT&CK -/** ************************************************************************************************* */ -const toolWithStixFields = { - ...validTool, - kill_chain_phases: [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - aliases: [ - "Sliver" - ], - tool_types: ["remote-access"], - tool_version: "1.0" -}; - -console.log("\nExample 6 - Tool with fields in STIX but not in ATT&CK:"); -console.log(toolSchema.parse(toolWithStixFields)); - -/** ************************************************************************************************* */ -// Example 7: Parsing the provided example tool -/** ************************************************************************************************* */ - -const exampleOfRealTool = { - "type": "tool", - "id": "tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2021-07-30T15:43:17.770Z", - "modified": "2024-04-11T00:06:01.264Z", - "name": "Sliver", - "description": "[Sliver](https://attack.mitre.org/software/S0633) is an open source, cross-platform, red team command and control framework written in Golang.(Citation: Bishop Fox Sliver Framework August 2019)", - "labels": [ - "tool" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0633", - "external_id": "S0633" - }, - { - "source_name": "Bishop Fox Sliver Framework August 2019", - "description": "Kervella, R. (2019, August 4). Cross-platform General Purpose Implant Framework Written in Golang. Retrieved July 30, 2021.", - "url": "https://labs.bishopfox.com/tech-blog/sliver" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "Sliver" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_contributors": [ - "Achute Sharma, Keysight", - "Ayan Saha, Keysight" - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows", - "Linux", - "macOS" - ], - "x_mitre_version": "1.2", - "spec_version": "2.0" -} - -console.log("\nExample 7 - Parsing the provided example tool:"); -const et7 = toolSchema.safeParse(exampleOfRealTool); -if (et7.success) { - console.log(et7.data); - console.log("Parsed successfully. Tool name:", et7.data.name); -} else { - console.log(z.prettifyError(et7.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 8: Tool with unknown property -/** ************************************************************************************************* */ -const toolWithUnknownProperty = { - ...exampleOfRealTool, - foo: 'bar' -} - -console.log("\nExample 8 - Parsing a tool with an unknown property (foo: 'bar'):"); -const et8 = toolSchema.safeParse(toolWithUnknownProperty); -if (et8.success) { - console.log("Parsed successfully. Tool name:", et8.data.name); -} else { - console.log(z.prettifyError(et8.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/stix-bundle.example.ts b/examples/sdo/stix-bundle.example.ts deleted file mode 100644 index f0527b63..00000000 --- a/examples/sdo/stix-bundle.example.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { z } from "zod/v4"; -import { stixBundleSchema } from '../../src/schemas/sdo/stix-bundle.schema.js'; -import type { StixCreatedTimestamp, StixModifiedTimestamp } from "../../src/schemas/common/index.js"; -import { v4 as uuidv4 } from 'uuid'; -import { identitySchema } from "../../src/schemas/sdo/identity.schema.js"; -import { assetSchema } from "../../src/schemas/sdo/asset.schema.js"; -import { campaignSchema } from "../../src/schemas/sdo/campaign.schema.js"; -import { malwareSchema } from "../../src/schemas/sdo/malware.schema.js"; -import { matrixSchema } from "../../src/schemas/sdo/matrix.schema.js"; -import { toolSchema } from "../../src/schemas/sdo/tool.schema.js"; -import { groupSchema } from "../../src/schemas/sdo/group.schema.js"; -import { mitigationSchema } from "../../src/schemas/sdo/mitigation.schema.js"; -import { dataComponentSchema } from "../../src/schemas/sdo/data-component.schema.js"; -import { dataSourceSchema } from "../../src/schemas/sdo/data-source.schema.js"; -import { tacticSchema } from "../../src/schemas/sdo/tactic.schema.js"; -import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; -import { collectionSchema } from "../../src/schemas/sdo/collection.schema.js"; -import { markingDefinitionSchema } from "../../src/schemas/smo/marking-definition.schema.js"; -import { relationshipSchema } from "../../src/schemas/sro/relationship.schema.js"; - -const StixObjectSchema: {[key: string]: z.ZodSchema} = { - "x-mitre-asset": assetSchema, - "campaign": campaignSchema, - "x-mitre-collection": collectionSchema, - "x-mitre-data-component": dataComponentSchema, - "x-mitre-data-source": dataSourceSchema, - "intrusion-set": groupSchema, - "identity": identitySchema, - "malware": malwareSchema, - "marking-definition": markingDefinitionSchema, - "x-mitre-matrix": matrixSchema, - "course-of-action": mitigationSchema, - "x-mitre-tactic": tacticSchema, - "attack-pattern": techniqueSchema, - "tool": toolSchema, - "relationship": relationshipSchema -}; - -/** ************************************************************************************************* */ -// Example 1: Valid Stix Bundle -/** ************************************************************************************************* */ - -const minimalCollection = { - id: `x-mitre-collection--${uuidv4()}`, - type: 'x-mitre-collection', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - modified: '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - name: 'Test Collection', - description: 'This is a test collection.', - x_mitre_attack_spec_version: "2.1.0", - x_mitre_version: "1.0", - x_mitre_contents: [ - { - object_ref: "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - object_modified: "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] -}; - -const validBundle = { - id: `bundle--${uuidv4()}`, - type: 'bundle', - spec_version: '2.1', - objects: [minimalCollection], -}; - -console.log("Example 1 - Valid Stix Bundle:"); -console.log(stixBundleSchema.parse(validBundle)); - -/** ************************************************************************************************* */ -// Example 2: Invalid Stix Bundle (missing required fields) -/** ************************************************************************************************* */ - -const invalidBundle = { - id: `bundle--${uuidv4()}`, - objects: [minimalCollection] -}; - -console.log("Example 2 - Invalid Stix Bundle (missing required fields):"); -const e2 = stixBundleSchema.safeParse(invalidBundle); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 3: Invalid Stix Bundle (missing required fields) -/** ************************************************************************************************* */ - -const bundleWithInvalidCollection = { - id: `bundle--${uuidv4()}`, - type: 'bundle', - spec_version: '2.1', - objects: [ - { - id: `x-mitre-collection--${uuidv4()}`, - type: 'x-mitre-collection', - spec_version: '2.1', - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - created: '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - modified: '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - name: 'Test Collection', - description: 'This is a test collection.', - x_mitre_attack_spec_version: "2.1.0", - x_mitre_version: "1.0", - x_mitre_contents: [ - { - object_ref: "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - object_modified: "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] - }, - { - type: "identity", - id: `identity--${uuidv4()}`, - spec_version: "2.3", - created: "2017-06-01T00:00:00.000Z" as StixCreatedTimestamp, - modified: "2017-06-01T00:00:00.000Z" as StixModifiedTimestamp, - name: "The MITRE Corporation", - object_marking_refs: [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - x_mitre_attack_spec_version: "2.1.0", - x_mitre_domains: ["enterprise-attack"], - x_mitre_version: "1.0" - } - ] -}; -console.log("Example 3 - Invalid Collection (missing required fields):"); - -const e3 = stixBundleSchema.safeParse(bundleWithInvalidCollection); -if (!e3.success) { - const errors: string[] = []; - e3.error.issues.forEach((issue) => { - const objectIndex = issue.path.find((p) => typeof p === 'number'); - const errorObject = objectIndex !== undefined ? bundleWithInvalidCollection.objects[objectIndex as number] : undefined; - console.log("\n") - let errorMessage = `Error in bundle`; - let objectMessage = `Validation errors: `; - if (errorObject) { - errorMessage += `\n Object Index: ${objectIndex}`; - errorMessage += `\n Object ID: ${errorObject.id}`; - errorMessage += `\n Object Type: ${errorObject.type}`; - errorMessage += `\n Object Name: ${(errorObject as any).name || 'N/A'}`; - - let objectStatus = 'Active'; - if ((errorObject as any).x_mitre_deprecated) { - objectStatus = 'Deprecated'; - } - errorMessage += `\n Object Status: ${objectStatus}`; - const schema = StixObjectSchema[errorObject.type]; - const objValidation = schema.safeParse(errorObject); - if (!objValidation.success) { - objectMessage += objValidation.error.issues.map(err => `\n - ${err.path.join('.')} : ${err.message}`).join(''); - } - } - errorMessage += `\n Path: ${issue.path.join('.')}`; - errorMessage += `\n Error: ${issue.message}`; - errors.push(errorMessage); - console.warn(errorMessage); - console.warn(objectMessage); - }); -} - -/** ************************************************************************************************* */ -// Example 4: Stix Bundle with invalid type -/** ************************************************************************************************* */ -const stixBundleWithInvalidType = { - ...validBundle, - type: "invalid-type", -}; - -console.log("\nExample 4 - Stix Bundle with invalid type:"); -const e4 = stixBundleSchema.safeParse(stixBundleWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 5: Stix Bundle with invalid id -/** ************************************************************************************************* */ -const stixBundleWithInvalidId = { - ...validBundle, - id: 'tool--11f8d7eb-1927-4806-9267-3a11d4d4d6be', -}; - -console.log("\nExample 5 - Stix Bundle with invalid id:"); -const e5 = stixBundleSchema.safeParse(stixBundleWithInvalidId); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 6: Parsing the provided example stix bundle -/** ************************************************************************************************* */ - -const exampleOfRealStixBundle = { - "id": `bundle--${uuidv4()}`, - "type": 'bundle', - "spec_version": '2.1', - "objects": [ - { - "id": `x-mitre-collection--${uuidv4()}`, - "type": 'x-mitre-collection', - "spec_version": '2.1', - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": '2021-01-01T00:00:00.000Z' as StixCreatedTimestamp, - "modified": '2021-01-01T00:00:00.000Z' as StixModifiedTimestamp, - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "name": 'Test Collection', - "description": 'This is a test collection.', - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_version": "1.0", - "x_mitre_contents": [ - { - "object_ref": "attack-pattern--0042a9f5-f053-4769-b3ef-9ad018dfa298", - "object_modified": "2021-01-01T00:00:00.000Z" as StixModifiedTimestamp - } - ] - }, - { - "type": "malware", - "id": "malware--2daa14d6-cbf3-4308-bb8e-213c324a08e4", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "created": "2017-05-31T21:32:29.203Z", - "modified": "2021-02-09T13:58:23.806Z", - "name": "HAMMERTOSS", - "description": "[HAMMERTOSS](https://attack.mitre.org/software/S0037) is a backdoor that was used by [APT29](https://attack.mitre.org/groups/G0016) in 2015. (Citation: FireEye APT29) (Citation: F-Secure The Dukes)", - "labels": [ - "malware" - ], - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/software/S0037", - "external_id": "S0037" - }, - { - "source_name": "FireEye APT29", - "description": "FireEye Labs. (2015, July). HAMMERTOSS: Stealthy Tactics Define a Russian Cyber Threat Group. Retrieved September 17, 2015.", - "url": "https://www2.fireeye.com/rs/848-DID-242/images/rpt-apt29-hammertoss.pdf" - }, - { - "source_name": "F-Secure The Dukes", - "description": "F-Secure Labs. (2015, September 17). The Dukes: 7 years of Russian cyberespionage. Retrieved December 10, 2015.", - "url": "https://www.f-secure.com/documents/996508/1030745/dukes_whitepaper.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_aliases": [ - "HAMMERTOSS", - "HammerDuke", - "NetDuke" - ], - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "Windows" - ], - "x_mitre_version": "1.2", - "spec_version": '2.1', - "is_family": false - } - ] -} - -console.log("\nExample 6 - Parsing the provided example stixBundle:"); -const e6 = stixBundleSchema.safeParse(exampleOfRealStixBundle); -if (e6.success) { - console.log(e6.data); - console.log("Parsed successfully. stix bundle ID:", e6.data.id); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} - -/** ************************************************************************************************* */ -// Example 7: Stix Bundle with unknown property -/** ************************************************************************************************* */ -const stixBundleWithUnknownProperty = { - ...exampleOfRealStixBundle, - foo: 'bar', -} - -console.log("\nExample 7 - Parsing a stix bundle with an unknown property (foo: 'bar'):"); -const e7 = stixBundleSchema.safeParse(stixBundleWithUnknownProperty); -if (e7.success) { - console.log("Parsed successfully. Stix Bundle name:", e7.data.id); -} else { - console.log(z.prettifyError(e7.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/tactic.example.ts b/examples/sdo/tactic.example.ts deleted file mode 100644 index 416d28dc..00000000 --- a/examples/sdo/tactic.example.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { z } from "zod/v4"; -import { tacticSchema } from "../../src/schemas/sdo/tactic.schema.js"; - -/****************************************************************************************************/ -// Example 1: Valid Tactic -/****************************************************************************************************/ -const validTactic = { - "id": "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "name": "Execution", - "description": "The adversary is trying to run malicious code.\n\nExecution consists of techniques that result in adversary-controlled code running on a local or remote system. Techniques that run malicious code are often paired with techniques from all other tactics to achieve broader goals, like exploring a network or stealing data. For example, an adversary might use a remote access tool to run a PowerShell script that does Remote System Discovery. ", - "external_references": [ - { - "external_id": "TA0002", - "url": "https://attack.mitre.org/tactics/TA0002", - "source_name": "mitre-attack" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_shortname": "execution", - "type": "x-mitre-tactic", - "modified": "2019-07-19T17:42:06.909Z", - "created": "2018-10-17T00:14:20.652Z", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0" -}; - -console.log("\nExample 1: Valid Tactic:"); -console.log(`SUCCESS ${tacticSchema.parse(validTactic).name}`) - -/****************************************************************************************************/ -// Example 2: Invalid Tactic (ATT&CK ID does not match format TA####) -/****************************************************************************************************/ -const invalidTacticID = { - ...validTactic, - external_references: [ - { - source_name: "mitre-attack", - external_id: "X0000" - } - ] -}; - -console.log("\nExample 2: Invalid Tactic (ATT&CK ID does not match format TA####):"); -const e2 = tacticSchema.safeParse(invalidTacticID); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 3: Invalid Tactic (missing required fields) -/****************************************************************************************************/ -const invalidTacticMissingFields = { - "id": "x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "description": "The adversary is trying to run malicious code.\n\nExecution consists of techniques that result in adversary-controlled code running on a local or remote system. Techniques that run malicious code are often paired with techniques from all other tactics to achieve broader goals, like exploring a network or stealing data. For example, an adversary might use a remote access tool to run a PowerShell script that does Remote System Discovery. ", - "external_references": [ - { - "external_id": "TA0002", - "url": "https://attack.mitre.org/tactics/TA0002", - "source_name": "mitre-attack" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "type": "x-mitre-tactic", - "modified": "2019-07-19T17:42:06.909Z", - "created": "2018-10-17T00:14:20.652Z", - "spec_version": "2.1", - "x_mitre_attack_spec_version": "2.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" -}; - -console.log("\nExample 3: Invalid Tactic (missing required fields):"); -const e3 = tacticSchema.safeParse(invalidTacticMissingFields); -console.log(z.prettifyError(e3.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 4: Tactic with invalid type -/****************************************************************************************************/ -const tacticWithInvalidType = { - ...validTactic, - type: "invalid-type" -}; - -console.log("\nExample 4: Tactic with invalid type:"); -const e4 = tacticSchema.safeParse(tacticWithInvalidType); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/****************************************************************************************************/ -// Example 5: Tactic with optional fields -/****************************************************************************************************/ -const tacticWithOptionalFields = { - ...validTactic, - x_mitre_deprecated: true -} - -console.log("\nExample 5: Tactic with optional fields:"); -console.log(tacticSchema.parse(tacticWithOptionalFields)); - -/** ************************************************************************************************* */ -// Example 6: Tactic with unknown property -/** ************************************************************************************************* */ -const tacticWithUnknownProperty = { - ...validTactic, - foo: 'bar' -} - -console.log("\nExample 6 - Parsing a tactic with an unknown property (foo: 'bar'):"); -const e6 = tacticSchema.safeParse(tacticWithUnknownProperty); -if (e6.success) { - console.log("Parsed successfully. Tactic name:", e6.data.name); -} else { - console.log(z.prettifyError(e6.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/sdo/technique.example.ts b/examples/sdo/technique.example.ts deleted file mode 100644 index 34cc8089..00000000 --- a/examples/sdo/technique.example.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { z } from "zod/v4"; -import { techniqueSchema } from "../../src/schemas/sdo/technique.schema.js"; - -/*************************************************************************************************** */ -// Example 1: Valid Technique -/*************************************************************************************************** */ -const validEnterpriseTechnique = { - "modified": "2024-02-02T19:04:35.389Z", - "name": "Data Obfuscation", - "description": "Adversaries may obfuscate command and control traffic to make it more difficult to detect.(Citation: Bitdefender FunnyDream Campaign November 2020) Command and control (C2) communications are hidden (but not necessarily encrypted) in an attempt to make the content more difficult to discover or decipher and to make the communication less conspicuous and hide commands from being seen. This encompasses many methods, such as adding junk data to protocol traffic, using steganography, or impersonating legitimate protocols. ", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "command-and-control" - } - ], - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "enterprise-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Linux", - "macOS", - "Windows" - ], - "x_mitre_version": "1.1", - "type": "attack-pattern", - "id": "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - "created": "2017-05-31T21:30:18.931Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "T1001" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 1 - Valid Technique:"); -console.log(`SUCCESS ${techniqueSchema.parse(validEnterpriseTechnique).name}`) - -/*************************************************************************************************** */ -// Example 2: Invalid Technique (ATT&CK ID does not match format T####) -/*************************************************************************************************** */ -const invalidTechniqueID = { - ...validEnterpriseTechnique, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "A00" - } - ], -}; - -console.log("\nExample 2 - Invalid Technique (ATT&CK ID does not match format T####):"); -const e2 = techniqueSchema.safeParse(invalidTechniqueID); -console.log(z.prettifyError(e2.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 3: Valid Sub-technique -/*************************************************************************************************** */ -const validSubtechnique = { - "modified": "2023-03-20T18:43:03.218Z", - "name": "Uninstall Malicious Application", - "description": "Adversaries may include functionality in malware that uninstalls the malicious application from the device. This can be achieved by: \n \n* Abusing device owner permissions to perform silent uninstallation using device owner API calls. \n* Abusing root permissions to delete files from the filesystem. \n* Abusing the accessibility service. This requires sending an intent to the system to request uninstallation, and then abusing the accessibility service to click the proper places on the screen to confirm uninstallation.", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-mobile-attack", - "phase_name": "defense-evasion" - } - ], - "x_mitre_deprecated": false, - "x_mitre_detection": "Users can see a list of applications that can use accessibility services in the device settings. Application vetting services could look for use of the accessibility service or features that typically require root access.", - "x_mitre_domains": [ - "mobile-attack" - ], - "x_mitre_is_subtechnique": true, - "x_mitre_platforms": [ - "Android" - ], - "x_mitre_version": "1.1", - "x_mitre_tactic_type": [ - "Post-Adversary Device Access" - ], - "type": "attack-pattern", - "id": "attack-pattern--0cdd66ad-26ac-4338-a764-4972a1e17ee3", - "created": "2022-03-30T19:31:31.855Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1630/001", - "external_id": "T1630.001" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-43.html", - "external_id": "APP-43" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -} -console.log("\nExample 3 - Valid Subtechnique:"); -console.log(`SUCCESS ${techniqueSchema.parse(validSubtechnique).name}`) - -/*************************************************************************************************** */ -// Example 4: Invalid Sub-technique (ATT&CK ID does not match format T####.###) -/*************************************************************************************************** */ -const invalidSubtechniqueID = { - ...validSubtechnique, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1630/001", - "external_id": "T1630" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-43.html", - "external_id": "APP-43" - } - ], -} - -console.log("\nExample 4 - Invalid Subtechnique (ATT&CK ID does not match format T####.###):"); -const e4 = techniqueSchema.safeParse(invalidSubtechniqueID); -console.log(z.prettifyError(e4.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 5: Invalid Technique (missing required fields) -/*************************************************************************************************** */ -const invalidTechniqueMissingFields = { - "modified": "2024-02-02T19:04:35.389Z", - "description": "Adversaries may obfuscate command and control traffic to make it more difficult to detect.(Citation: Bitdefender FunnyDream Campaign November 2020) Command and control (C2) communications are hidden (but not necessarily encrypted) in an attempt to make the content more difficult to discover or decipher and to make the communication less conspicuous and hide commands from being seen. This encompasses many methods, such as adding junk data to protocol traffic, using steganography, or impersonating legitimate protocols. ", - "x_mitre_deprecated": false, - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Linux", - "macOS", - "Windows" - ], - "type": "attack-pattern", - "id": "attack-pattern--ad255bfe-a9e6-4b52-a258-8d3462abe842", - "created": "2017-05-31T21:30:18.931Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1001", - "external_id": "T1001" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -}; - -console.log("\nExample 5 - Invalid Technique (missing required fields):"); -const e5 = techniqueSchema.safeParse(invalidTechniqueMissingFields); -console.log(z.prettifyError(e5.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 6: Technique with invalid type -/*************************************************************************************************** */ -const techniqueWithInvalidType = { - ...validEnterpriseTechnique, - "type": 'invalid-type' -} - -console.log("\nExample 6 - Technique with invalid type:"); -const e6 = techniqueSchema.safeParse(techniqueWithInvalidType); -console.log(z.prettifyError(e6.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 7: Valid Enterprise Technique with Enterprise-only fields -/*************************************************************************************************** */ -const validTechniqueWithEnterpriseFields = { - ...validEnterpriseTechnique, - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "privilege-escalation" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "defense-evasion" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "execution" - }, - { - "kill_chain_name": "mitre-attack", - "phase_name": "impact" - } - ], - "x_mitre_system_requirements": ["Windows 10"], - "x_mitre_permissions_required": ["User"], - "x_mitre_effective_permissions": ["Administrator"], - "x_mitre_defense_bypassed": ["Anti-virus"], - "x_mitre_remote_support": true, - "x_mitre_impact_type": ["Integrity"] -}; -console.log("\nExample 7: Valid Enterprise Technique with Enterprise-only fields:"); -let result = techniqueSchema.parse(validTechniqueWithEnterpriseFields); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 8: Invalid Enterprise Technique with Mobile-only fields -/*************************************************************************************************** */ -const invalidEnterpriseTechnique = { - ...validEnterpriseTechnique, - "x_mitre_tactic_type": ["Post-Adversary Device Access"], -}; - -console.log("\nExample 8: Invalid Enterprise Technique with Mobile-only fields:"); -const e8 = techniqueSchema.safeParse(invalidEnterpriseTechnique); -console.log(z.prettifyError(e8.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 9: Valid Mobile Technique with Mobile-only fields -/*************************************************************************************************** */ -const validMobileTechnique = { - "modified": "2023-03-15T16:23:59.281Z", - "name": "Abuse Elevation Control Mechanism", - "description": "Adversaries may circumvent mechanisms designed to control elevated privileges to gain higher-level permissions. Most modern systems contain native elevation control mechanisms that are intended to limit privileges that a user can gain on a machine. Authorization has to be granted to specific users in order to perform tasks that are designated as higher risk. An adversary can use several methods to take advantage of built-in control mechanisms in order to escalate privileges on a system. ", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-mobile-attack", - "phase_name": "privilege-escalation" - } - ], - "x_mitre_deprecated": false, - "x_mitre_detection": "When an application requests administrator permission, users are presented with a popup and the option to grant or deny the request. Application vetting services can detect when an application requests administrator permission. Extra scrutiny could be applied to applications that do", - "x_mitre_domains": [ - "mobile-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_platforms": [ - "Android" - ], - "x_mitre_version": "1.1", - "x_mitre_tactic_type": [ - "Post-Adversary Device Access" - ], - "type": "attack-pattern", - "id": "attack-pattern--08ea902d-ecb5-47ed-a453-2798057bb2d3", - "created": "2022-04-01T15:54:05.633Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T1626", - "external_id": "T1626" - }, - { - "source_name": "NIST Mobile Threat Catalogue", - "url": "https://pages.nist.gov/mobile-threat-catalogue/application-threats/APP-22.html", - "external_id": "APP-22" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "x_mitre_attack_spec_version": "3.1.0", - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "spec_version": "2.1" -} - -console.log("\nExample 9: Valid Mobile Technique with Mobile-only fields:"); -result = techniqueSchema.parse(validMobileTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 10: Invalid Mobile Technique with Enterprise-only fields -/*************************************************************************************************** */ -const invalidMobileTechnique = { - ...validMobileTechnique, - "x_mitre_system_requirements": ["system requirements"] -} - -console.log("\nExample 10: Invalid Mobile Technique with Enterprise-only fields:"); -const e10 = techniqueSchema.safeParse(invalidMobileTechnique); -console.log(z.prettifyError(e10.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 11: Valid ICS Technique with ICS-only fields -/*************************************************************************************************** */ -const validIcsTechnique = { - "modified": "2023-10-13T17:56:58.380Z", - "name": "Block Command Message", - "description": "Adversaries may block a command message from reaching its intended target to prevent command execution. In OT networks, command messages are sent to provide instructions to control system devices. A blocked command message can inhibit response functions from correcting a disruption or unsafe condition. (Citation: Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011) (Citation: Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems March 2016)", - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-ics-attack", - "phase_name": "inhibit-response-function" - } - ], - "x_mitre_attack_spec_version": "3.2.0", - "x_mitre_deprecated": false, - "x_mitre_domains": [ - "ics-attack" - ], - "x_mitre_is_subtechnique": false, - "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_platforms": [ - "None" - ], - "x_mitre_version": "1.1", - "x_mitre_data_sources": [ - "Process: Process Termination", - "Operational Databases: Process History/Live Data", - "Application Log: Application Log Content", - "Network Traffic: Network Traffic Flow", - "Operational Databases: Process/Event Alarm" - ], - "type": "attack-pattern", - "id": "attack-pattern--008b8f56-6107-48be-aa9f-746f927dbb61", - "created": "2020-05-21T17:43:26.506Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "revoked": false, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T0803", - "external_id": "T0803" - }, - { - "source_name": "Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011", - "description": "Bonnie Zhu, Anthony Joseph, Shankar Sastry 2011 A Taxonomy of Cyber Attacks on SCADA Systems Retrieved. 2018/01/12 ", - "url": "http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6142258" - }, - { - "source_name": "Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems March 2016", - "description": "Electricity Information Sharing and Analysis Center; SANS Industrial Control Systems 2016, March 18 Analysis of the Cyber Attack on the Ukranian Power Grid: Defense Use Case Retrieved. 2018/03/27 ", - "url": "https://assets.contentstack.io/v3/assets/blt36c2e63521272fdc/blt6a77276749b76a40/607f235992f0063e5c070fff/E-ISAC_SANS_Ukraine_DUC_5%5b73%5d.pdf" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], - "spec_version": "2.1" -} - -console.log("\nExample 11: Valid ICS Technique with ICS-only fields:"); -result = techniqueSchema.parse(validIcsTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 12: Invalid ICS Technique with Enterprise-only fields -/*************************************************************************************************** */ -const invalidIcsTechnique = { - ...validIcsTechnique, - "x_mitre_permissions_required": ["permissions required"] -} - -console.log("\nExample 12: Invalid ICS Technique with Enterprise-only fields:"); -const e12 = techniqueSchema.safeParse(invalidIcsTechnique); -console.log(z.prettifyError(e12.error as z.core.$ZodError)); - -/*************************************************************************************************** */ -// Example 13: Valid multi-domain Technique with Enterprise/ICS-only fields -/*************************************************************************************************** */ -const validMultiDomainTechnique = { - ...validEnterpriseTechnique, - "x_mitre_domains": [ - "enterprise-attack", - "ics-attack" - ], - "x_mitre_data_sources": [ - "Process: Process Termination", - "Operational Databases: Process History/Live Data", - "Application Log: Application Log Content", - "Network Traffic: Network Traffic Flow", - "Operational Databases: Process/Event Alarm" - ], -} - -console.log("\nExample 13: Valid multi-domain Technique with Enterprise/ICS-only fields:"); -result = techniqueSchema.parse(validMultiDomainTechnique); -console.log(`SUCCESS ${result.name} (${result.x_mitre_domains})`) - -/*************************************************************************************************** */ -// Example 14: Enterprise-only fields in the wrong tactic -/*************************************************************************************************** */ -console.log("\nExample 14: Invalid Enterprise Technique with Enterprise-only field in wrong tactic:"); -const invalidEnterpriseTechniqueWrongTactic = { - ...validEnterpriseTechnique, - "kill_chain_phases": [ - { - "kill_chain_name": "mitre-attack", - "phase_name": "execution" - } - ], - "x_mitre_permissions_required": ["User"] -}; - -const e14 = techniqueSchema.safeParse(invalidEnterpriseTechniqueWrongTactic); -console.log(z.prettifyError(e14.error as z.core.$ZodError)); - -/** ************************************************************************************************* */ -// Example 15: Technique with unknown property -/** ************************************************************************************************* */ -const techniqueWithUnknownProperty = { - ...validEnterpriseTechnique, - foo: 'bar' -} - -console.log("\nExample 15 - Parsing a technique with an unknown property (foo: 'bar'):"); -const e15 = techniqueSchema.safeParse(techniqueWithUnknownProperty); -if (e15.success) { - console.log("Parsed successfully. Technique name:", e15.data.name); -} else { - console.log(z.prettifyError(e15.error as z.core.$ZodError)); -} \ No newline at end of file diff --git a/examples/smo/marking-definition.example.ts b/examples/smo/marking-definition.example.ts deleted file mode 100644 index de072770..00000000 --- a/examples/smo/marking-definition.example.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { markingDefinitionSchema } from "../../src/schemas/smo/marking-definition.schema.js"; -import { z } from "zod"; - -/** ************************************************************************************************* */ -// Example 1: Valid Marking Definition -/** ************************************************************************************************* */ -const validMarkingDefinition = { - definition: { - statement: "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - created: "2017-06-01T00:00:00.000Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - definition_type: "statement", - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("Example 1 - Valid Marking Definition:"); -console.log(markingDefinitionSchema.parse(validMarkingDefinition)); -// { -// id: 'marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168', -// type: 'marking-definition', -// spec_version: '2.1', -// created: '2017-06-01T00:00:00.000Z', -// created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', -// definition_type: 'statement', -// definition: { -// statement: 'Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.' -// }, -// x_mitre_domains: [ 'mobile-attack' ], -// x_mitre_attack_spec_version: '2.1.0' -// } - - -/** ************************************************************************************************* */ -// Example 2: Invalid Marking Definition (missing required fields) -/** ************************************************************************************************* */ -const invalidMarkingDefinition = { - definition: { - statement: - "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - // missing created - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - // missing definition_type - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log( - "\nExample 2 - Invalid Marking Definition (missing required fields):" -); -try { - markingDefinitionSchema.parse(invalidMarkingDefinition); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Validation errors: [ -// { -// code: 'custom', -// message: "Invalid STIX timestamp format: must be an RFC3339 timestamp with a timezone specification of 'Z'.", -// fatal: true, -// path: [ 'created' ] -// }, -// { -// expected: "'statement' | 'tlp'", -// received: 'undefined', -// code: 'invalid_type', -// path: [ 'definition_type' ], -// message: "definition_type must be either 'statement' or 'tlp'" -// } -// ] - -/** ************************************************************************************************* */ -// Example 3: Marking Definition with invalid fields -/** ************************************************************************************************* */ -const invalidDefinitionStatement = { - statement: "Example statement", - name: "Example name", // <--- This property is not allowed on definition statements - external_references: [ // <--- This property is not allowed on definition statements - { - source_name: "mitre-attack", - url: "https://attack.mitre.org/software/S0698", - external_id: "S0698", - }, - ], - object_marking_refs: [ // <--- This property is not allowed on definition statements - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - ], - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", // <--- This property is not allowed on definition statements -} - -const markingDefinitionWithOptionalFields = { - ...validMarkingDefinition, - definition: invalidDefinitionStatement, -}; - -console.log("\nExample 3 - Marking Definition with optional fields:"); -try { - markingDefinitionSchema.parse(markingDefinitionWithOptionalFields); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Validation errors: [ -// { -// code: 'unrecognized_keys', -// keys: [ -// 'name', -// 'external_references', -// 'object_marking_refs', -// 'created_by_ref' -// ], -// path: [ 'definition' ], -// message: "Unrecognized key(s) in object: 'name', 'external_references', 'object_marking_refs', 'created_by_ref'" -// } -// ] - -/** ************************************************************************************************* */ -// Example 4: Marking Definition with invalid type -/** ************************************************************************************************* */ -const markingDefinitionWithInvalidType = { - ...validMarkingDefinition, - type: "invalid-type", -}; - -console.log("\nExample 4 - Marking Definition with invalid type:"); -try { - markingDefinitionSchema.parse(markingDefinitionWithInvalidType); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation error:", error.errors[0].message); - // Validation error: Invalid literal value, expected "marking-definition" - } -} - -/** ************************************************************************************************* */ -// Example 5: Parsing the provided example Marking Definition -/** ************************************************************************************************* */ -const exampleOfRealMarkingDefinition = { - definition: { - statement: - "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation.", - }, - id: "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168", - type: "marking-definition", - created: "2017-06-01T00:00:00.000Z", - created_by_ref: "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - definition_type: "statement", - x_mitre_attack_spec_version: "2.1.0", - spec_version: "2.1", - x_mitre_domains: ["mobile-attack"], -}; - -console.log("\nExample 5 - Parsing the provided example Marking Definition:"); -try { - const parsedMarkingDefinition = markingDefinitionSchema.parse( - exampleOfRealMarkingDefinition - ); - console.log(`Parsed successfully. marking definition id: ${parsedMarkingDefinition.id}`); -} catch (error) { - if (error instanceof z.ZodError) { - console.log("Validation errors:", error.errors); - } -} -// Parsed successfully. Marking Definition id: marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168 diff --git a/scripts/validate-local-stix21-bundle.ts b/examples/validate-stix/validate-local-stix21-bundle.ts similarity index 84% rename from scripts/validate-local-stix21-bundle.ts rename to examples/validate-stix/validate-local-stix21-bundle.ts index d87b5e3c..33b3bbaf 100644 --- a/scripts/validate-local-stix21-bundle.ts +++ b/examples/validate-stix/validate-local-stix21-bundle.ts @@ -38,7 +38,7 @@ import fs from 'fs/promises'; import path from 'path'; import { z } from 'zod'; -import { stixBundleSchema, type StixBundle } from '../src/schemas/sdo/stix-bundle.schema'; +import { stixBundleSchema, type StixBundle } from '@mitre-attack/attack-data-model'; /** * Formats a ZodError into a readable string with context about the failing objects @@ -147,24 +147,28 @@ function formatZodError(error: z.ZodError, bundle: StixBundle): string { function formatError(issue: z.ZodIssue): string { // For enum validation errors, reformat to include the received value clearly if (issue.code === 'invalid_enum_value' && issue.received) { - return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map(opt => `'${opt}'`).join(' | ')}`; + return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map((opt) => `'${opt}'`).join(' | ')}`; } - + // For invalid arguments, include value clearly if (issue.code === 'invalid_arguments' && issue.argumentsError) { return `Invalid arguments: ${issue.message}`; } // For custom validation errors that contain 'Expected X, received Y' format - if (issue.code === 'custom' && issue.message.includes('Expected') && issue.message.includes('received')) { + if ( + issue.code === 'custom' && + issue.message.includes('Expected') && + issue.message.includes('received') + ) { return issue.message; } - + // Handle invalid types more clearly if (issue.code === 'invalid_type') { return `Invalid type. Expected ${issue.expected}, received ${issue.received}`; } - + // Return the original message for other types of errors return issue.message; } @@ -174,40 +178,58 @@ function formatError(issue: z.ZodIssue): string { * @returns Structured data about validation errors for analysis */ function collectErrorStatistics( - error: z.ZodError, - bundle: StixBundle + error: z.ZodError, + bundle: StixBundle, ): { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; } { - const invalidEnumValues = new Map(); + const invalidEnumValues = new Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >(); const errorsByPath = new Map(); const errorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const errorsByType: Record = {}; error.issues.forEach((issue) => { // Get object information if this is an object-level error - let objectId = "unknown"; - let objectName = "unknown"; - let objectType = "unknown"; - let objectStatus = "unknown"; - + let objectId = 'unknown'; + let objectName = 'unknown'; + let objectType = 'unknown'; + let objectStatus = 'unknown'; + const objectsIndex = issue.path.findIndex((segment) => segment === 'objects'); if (objectsIndex !== -1 && objectsIndex + 1 < issue.path.length) { const objectIndex = issue.path[objectsIndex + 1]; if (typeof objectIndex === 'number' && objectIndex < bundle.objects.length) { const errorObject = bundle.objects[objectIndex]; - + objectId = errorObject.id; objectType = errorObject.type; objectName = (errorObject as any).name || 'Unnamed'; - + // Determine object status objectStatus = 'Active'; if ((errorObject as any).x_mitre_deprecated) { @@ -215,10 +237,10 @@ function collectErrorStatistics( } else if ('revoked' in errorObject && (errorObject as any).revoked) { objectStatus = 'Revoked'; } - + // Count by status errorsByStatus[objectStatus] = (errorsByStatus[objectStatus] || 0) + 1; - + // Count by type errorsByType[objectType] = (errorsByType[objectType] || 0) + 1; } @@ -230,13 +252,13 @@ function collectErrorStatistics( if (!invalidEnumValues.has(pathKey)) { invalidEnumValues.set(pathKey, []); } - + invalidEnumValues.get(pathKey)?.push({ value: String(issue.received), objectId, objectName, objectType, - objectStatus + objectStatus, }); } @@ -253,120 +275,137 @@ function collectErrorStatistics( */ function formatErrorAggregateReport( stats: { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; }, totalErrorCount: number, - bundleName: string + bundleName: string, ): string { const report: string[] = []; - + report.push(`\n=== ERROR AGGREGATION REPORT FOR ${bundleName} ===`); report.push(`Total validation issues: ${totalErrorCount}\n`); - + // Report errors by status report.push(`Errors by object status:`); for (const [status, count] of Object.entries(stats.errorsByStatus)) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${status}: ${count} (${percentage}%)`); } report.push(''); - + // Report errors by type report.push(`Errors by object type:`); - const sortedTypeEntries = Object.entries(stats.errorsByType) - .sort(([, countA], [, countB]) => countB - countA); - + const sortedTypeEntries = Object.entries(stats.errorsByType).sort( + ([, countA], [, countB]) => countB - countA, + ); + for (const [type, count] of sortedTypeEntries) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${type}: ${count} (${percentage}%)`); } report.push(''); - + // Report top error paths report.push(`Top error paths:`); const sortedPathEntries = [...stats.errorsByPath.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 10); - + for (const [path, count] of sortedPathEntries) { - const percentage = totalErrorCount > 0 ? (count / totalErrorCount * 100).toFixed(1) : '0.0'; + const percentage = totalErrorCount > 0 ? ((count / totalErrorCount) * 100).toFixed(1) : '0.0'; report.push(` ${path}: ${count} (${percentage}%)`); } report.push(''); - + // Report invalid enum values report.push(`Invalid enum values by property:`); - + // Group by property first - const propertiesByEnumValue = new Map(); - + const propertiesByEnumValue = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] } + >(); + for (const [path, values] of stats.invalidEnumValues.entries()) { // Extract the property name from the path const pathParts = path.split('.'); const propertyName = pathParts.slice(2).join('.'); - + for (const valueInfo of values) { const valueKey = `${propertyName}|${valueInfo.value}`; - + if (!propertiesByEnumValue.has(valueKey)) { propertiesByEnumValue.set(valueKey, { value: valueInfo.value, - objects: [] + objects: [], }); } - + // Only add this object if it's not already in the list const existingObjects = propertiesByEnumValue.get(valueKey)!.objects; - if (!existingObjects.some(obj => obj.id === valueInfo.objectId)) { + if (!existingObjects.some((obj) => obj.id === valueInfo.objectId)) { existingObjects.push({ id: valueInfo.objectId, name: valueInfo.objectName, type: valueInfo.objectType, - status: valueInfo.objectStatus + status: valueInfo.objectStatus, }); } } } - + // Group properties by their name - const groupedProperties = new Map(); - + const groupedProperties = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] }[] + >(); + for (const [key, valueInfo] of propertiesByEnumValue.entries()) { const propertyName = key.split('|')[0]; - + if (!groupedProperties.has(propertyName)) { groupedProperties.set(propertyName, []); } - + groupedProperties.get(propertyName)!.push(valueInfo); } - + // Output the grouped invalid enum values for (const [propertyName, valueInfos] of groupedProperties.entries()) { report.push(` Property: ${propertyName}`); - + for (const valueInfo of valueInfos) { report.push(` Invalid value: '${valueInfo.value}'`); report.push(` Found in ${valueInfo.objects.length} objects:`); - + // Sort objects by name for more consistent output const sortedObjects = [...valueInfo.objects].sort((a, b) => a.name.localeCompare(b.name)); - - for (const obj of sortedObjects.slice(0, 10)) { // Limit to first 10 objects for brevity + + for (const obj of sortedObjects.slice(0, 10)) { + // Limit to first 10 objects for brevity report.push(` - ${obj.name} (${obj.id}) [${obj.type}, ${obj.status}]`); } - + if (sortedObjects.length > 10) { report.push(` ... and ${sortedObjects.length - 10} more objects`); } - + report.push(''); } } - + return report.join('\n'); } @@ -452,7 +491,9 @@ async function validateLocalStixBundles() { const filePath = fileArg.split('=')[1]; if (!filePath) { - console.error('Error: Invalid file path. Please provide a valid path to a STIX bundle JSON file.'); + console.error( + 'Error: Invalid file path. Please provide a valid path to a STIX bundle JSON file.', + ); process.exit(1); } @@ -474,12 +515,8 @@ async function validateLocalStixBundles() { // Generate error statistics report if there's an error if (result.error) { const stats = collectErrorStatistics(result.error, result.bundle); - const statsReport = formatErrorAggregateReport( - stats, - result.errorCount, - result.fileName - ); - + const statsReport = formatErrorAggregateReport(stats, result.errorCount, result.fileName); + await fs.writeFile(statsFilePath, statsReport); console.log(`Aggregate error statistics written to: ${statsFilePath}`); console.log(statsReport); // Also print to console @@ -507,7 +544,6 @@ async function validateLocalStixBundles() { .forEach(([type, count]) => { console.log(` ${type}: ${count}`); }); - } catch (error) { console.error('Error validating STIX bundle:', error); process.exit(1); @@ -515,4 +551,4 @@ async function validateLocalStixBundles() { } // Run the validation -validateLocalStixBundles().catch(console.error); \ No newline at end of file +validateLocalStixBundles().catch(console.error); diff --git a/scripts/validate-prod-stix21-bundles.ts b/examples/validate-stix/validate-prod-stix21-bundles.ts similarity index 86% rename from scripts/validate-prod-stix21-bundles.ts rename to examples/validate-stix/validate-prod-stix21-bundles.ts index 5fbe7573..537f4e20 100644 --- a/scripts/validate-prod-stix21-bundles.ts +++ b/examples/validate-stix/validate-prod-stix21-bundles.ts @@ -38,8 +38,12 @@ import axios from 'axios'; import fs from 'fs/promises'; import { z } from 'zod'; -import { stixBundleSchema, type StixBundle } from '../src/schemas/sdo/stix-bundle.schema'; -import { type AttackDomain, attackDomainSchema } from '../src/schemas/common'; +import { + attackDomainSchema, + stixBundleSchema, + type AttackDomain, + type StixBundle, +} from '@mitre-attack/attack-data-model'; const SUPPORTED_DOMAINS = attackDomainSchema.options; @@ -150,24 +154,28 @@ function formatZodError(error: z.ZodError, bundle: StixBundle): string { function formatError(issue: z.ZodIssue): string { // For enum validation errors, reformat to include the received value clearly if (issue.code === 'invalid_enum_value' && issue.received) { - return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map(opt => `'${opt}'`).join(' | ')}`; + return `Invalid enum value. Received '${issue.received}' but expected one of: ${issue.options.map((opt) => `'${opt}'`).join(' | ')}`; } - + // For invalid arguments, include value clearly if (issue.code === 'invalid_arguments' && issue.argumentsError) { return `Invalid arguments: ${issue.message}`; } // For custom validation errors that contain 'Expected X, received Y' format - if (issue.code === 'custom' && issue.message.includes('Expected') && issue.message.includes('received')) { + if ( + issue.code === 'custom' && + issue.message.includes('Expected') && + issue.message.includes('received') + ) { return issue.message; } - + // Handle invalid types more clearly if (issue.code === 'invalid_type') { return `Invalid type. Expected ${issue.expected}, received ${issue.received}`; } - + // Return the original message for other types of errors return issue.message; } @@ -244,40 +252,58 @@ async function validateStix21Bundle(domain: AttackDomain): Promise<{ * @returns Structured data about validation errors for analysis */ function collectErrorStatistics( - error: z.ZodError, - bundle: StixBundle + error: z.ZodError, + bundle: StixBundle, ): { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; } { - const invalidEnumValues = new Map(); + const invalidEnumValues = new Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >(); const errorsByPath = new Map(); const errorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const errorsByType: Record = {}; error.issues.forEach((issue) => { // Get object information if this is an object-level error - let objectId = "unknown"; - let objectName = "unknown"; - let objectType = "unknown"; - let objectStatus = "unknown"; - + let objectId = 'unknown'; + let objectName = 'unknown'; + let objectType = 'unknown'; + let objectStatus = 'unknown'; + const objectsIndex = issue.path.findIndex((segment) => segment === 'objects'); if (objectsIndex !== -1 && objectsIndex + 1 < issue.path.length) { const objectIndex = issue.path[objectsIndex + 1]; if (typeof objectIndex === 'number' && objectIndex < bundle.objects.length) { const errorObject = bundle.objects[objectIndex]; - + objectId = errorObject.id; objectType = errorObject.type; objectName = (errorObject as any).name || 'Unnamed'; - + // Determine object status objectStatus = 'Active'; if ((errorObject as any).x_mitre_deprecated) { @@ -285,10 +311,10 @@ function collectErrorStatistics( } else if ('revoked' in errorObject && (errorObject as any).revoked) { objectStatus = 'Revoked'; } - + // Count by status errorsByStatus[objectStatus] = (errorsByStatus[objectStatus] || 0) + 1; - + // Count by type errorsByType[objectType] = (errorsByType[objectType] || 0) + 1; } @@ -300,13 +326,13 @@ function collectErrorStatistics( if (!invalidEnumValues.has(pathKey)) { invalidEnumValues.set(pathKey, []); } - + invalidEnumValues.get(pathKey)?.push({ value: String(issue.received), objectId, objectName, objectType, - objectStatus + objectStatus, }); } @@ -323,119 +349,136 @@ function collectErrorStatistics( */ function formatErrorAggregateReport( stats: { - invalidEnumValues: Map, - errorsByPath: Map, - errorsByStatus: Record, - errorsByType: Record + invalidEnumValues: Map< + string, + { + value: string; + objectId: string; + objectName: string; + objectType: string; + objectStatus: string; + }[] + >; + errorsByPath: Map; + errorsByStatus: Record; + errorsByType: Record; }, - totalErrorCount: number + totalErrorCount: number, ): string { const report: string[] = []; - + report.push(`\n=== ERROR AGGREGATION REPORT ===`); report.push(`Total validation issues: ${totalErrorCount}\n`); - + // Report errors by status report.push(`Errors by object status:`); for (const [status, count] of Object.entries(stats.errorsByStatus)) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${status}: ${count} (${percentage}%)`); } report.push(''); - + // Report errors by type report.push(`Errors by object type:`); - const sortedTypeEntries = Object.entries(stats.errorsByType) - .sort(([, countA], [, countB]) => countB - countA); - + const sortedTypeEntries = Object.entries(stats.errorsByType).sort( + ([, countA], [, countB]) => countB - countA, + ); + for (const [type, count] of sortedTypeEntries) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${type}: ${count} (${percentage}%)`); } report.push(''); - + // Report top error paths report.push(`Top error paths:`); const sortedPathEntries = [...stats.errorsByPath.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 10); - + for (const [path, count] of sortedPathEntries) { - const percentage = (count / totalErrorCount * 100).toFixed(1); + const percentage = ((count / totalErrorCount) * 100).toFixed(1); report.push(` ${path}: ${count} (${percentage}%)`); } report.push(''); - + // Report invalid enum values report.push(`Invalid enum values by property:`); - + // Group by property first - const propertiesByEnumValue = new Map(); - + const propertiesByEnumValue = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] } + >(); + for (const [path, values] of stats.invalidEnumValues.entries()) { // Extract the property name from the path const pathParts = path.split('.'); const propertyName = pathParts.slice(2).join('.'); - + for (const valueInfo of values) { const valueKey = `${propertyName}|${valueInfo.value}`; - + if (!propertiesByEnumValue.has(valueKey)) { propertiesByEnumValue.set(valueKey, { value: valueInfo.value, - objects: [] + objects: [], }); } - + // Only add this object if it's not already in the list const existingObjects = propertiesByEnumValue.get(valueKey)!.objects; - if (!existingObjects.some(obj => obj.id === valueInfo.objectId)) { + if (!existingObjects.some((obj) => obj.id === valueInfo.objectId)) { existingObjects.push({ id: valueInfo.objectId, name: valueInfo.objectName, type: valueInfo.objectType, - status: valueInfo.objectStatus + status: valueInfo.objectStatus, }); } } } - + // Group properties by their name - const groupedProperties = new Map(); - + const groupedProperties = new Map< + string, + { value: string; objects: { id: string; name: string; type: string; status: string }[] }[] + >(); + for (const [key, valueInfo] of propertiesByEnumValue.entries()) { const propertyName = key.split('|')[0]; - + if (!groupedProperties.has(propertyName)) { groupedProperties.set(propertyName, []); } - + groupedProperties.get(propertyName)!.push(valueInfo); } - + // Output the grouped invalid enum values for (const [propertyName, valueInfos] of groupedProperties.entries()) { report.push(` Property: ${propertyName}`); - + for (const valueInfo of valueInfos) { report.push(` Invalid value: '${valueInfo.value}'`); report.push(` Found in ${valueInfo.objects.length} objects:`); - + // Sort objects by name for more consistent output const sortedObjects = [...valueInfo.objects].sort((a, b) => a.name.localeCompare(b.name)); - - for (const obj of sortedObjects.slice(0, 10)) { // Limit to first 10 objects for brevity + + for (const obj of sortedObjects.slice(0, 10)) { + // Limit to first 10 objects for brevity report.push(` - ${obj.name} (${obj.id}) [${obj.type}, ${obj.status}]`); } - + if (sortedObjects.length > 10) { report.push(` ... and ${sortedObjects.length - 10} more objects`); } - + report.push(''); } } - + return report.join('\n'); } @@ -466,19 +509,21 @@ async function validateStixBundles() { const statsFilePath = `./validation-stats-${timestamp}.txt`; // Validate each domain - const results = await Promise.all(requestedDomains.map((domain) => validateStix21Bundle(domain))); + const results = await Promise.all( + requestedDomains.map((domain) => validateStix21Bundle(domain)), + ); // Collect all error messages const allErrors: string[] = []; let totalErrorCount = 0; - + // For collecting aggregate error statistics const allInvalidEnumValues = new Map>(); const allErrorsByPath = new Map(); const allErrorsByStatus: Record = { - 'Active': 0, - 'Deprecated': 0, - 'Revoked': 0 + Active: 0, + Deprecated: 0, + Revoked: 0, }; const allErrorsByType: Record = {}; @@ -490,28 +535,28 @@ async function validateStixBundles() { ); allErrors.push(result.formattedError); totalErrorCount += result.errorCount; - + // Use the stored error from validation if (result.error) { // Collect statistics from the domain const domainStats = collectErrorStatistics(result.error, result.bundle); - + // Merge into overall statistics for (const [path, values] of domainStats.invalidEnumValues.entries()) { if (!allInvalidEnumValues.has(path)) { allInvalidEnumValues.set(path, new Set()); } - values.forEach(value => allInvalidEnumValues.get(path)?.add(value)); + values.forEach((value) => allInvalidEnumValues.get(path)?.add(value)); } - + for (const [path, count] of domainStats.errorsByPath.entries()) { allErrorsByPath.set(path, (allErrorsByPath.get(path) || 0) + count); } - + for (const [status, count] of Object.entries(domainStats.errorsByStatus)) { allErrorsByStatus[status] = (allErrorsByStatus[status] || 0) + count; } - + for (const [type, count] of Object.entries(domainStats.errorsByType)) { allErrorsByType[type] = (allErrorsByType[type] || 0) + count; } @@ -523,16 +568,16 @@ async function validateStixBundles() { if (allErrors.length > 0) { await fs.writeFile(errorFilePath, allErrors.join('\n')); console.log(`Full error details written to: ${errorFilePath}`); - + // Generate and write aggregate statistics report const statsReport = formatErrorAggregateReport( - { - invalidEnumValues: allInvalidEnumValues, + { + invalidEnumValues: allInvalidEnumValues, errorsByPath: allErrorsByPath, errorsByStatus: allErrorsByStatus, - errorsByType: allErrorsByType + errorsByType: allErrorsByType, }, - totalErrorCount + totalErrorCount, ); await fs.writeFile(statsFilePath, statsReport); console.log(`Aggregate error statistics written to: ${statsFilePath}`); diff --git a/generate-docs.sh b/generate-docs.sh index 38485da8..b4386b54 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -2,13 +2,13 @@ WORKDIR="." SCHEMA_DIR="$WORKDIR/src/schemas" -OUTPUT_DIR="docusaurus/docs" -OVERVIEW="$OUTPUT_DIR/overview.md" +OUTPUT_DIR="docusaurus/docs/reference/schemas" +OVERVIEW="$OUTPUT_DIR/index.mdx" mkdir -p $OUTPUT_DIR -# init overview.md -echo "# ATT&CK Schemas" > $OVERVIEW +# init index.md +echo "# Schema Reference" > $OVERVIEW echo "" >> $OVERVIEW # attack spec version @@ -30,7 +30,7 @@ find $SCHEMA_DIR -name "*.schema.ts" | while read schemaFile; do # skip stix-bundle (manually generated) and add to overview page if [[ "${fileName}" == "stix-bundle.schema.ts" ]]; then - echo "| STIX Bundle | SDO | [Schema](/docs/sdo/stix-bundle.schema) |" >> $OVERVIEW + echo "| STIX Bundle | SDO | [Schema](./sdo/stix-bundle.schema) |" >> $OVERVIEW continue fi @@ -42,16 +42,16 @@ find $SCHEMA_DIR -name "*.schema.ts" | while read schemaFile; do title=$(echo "$title" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1') # set output file path - outputFile="$OUTPUT_DIR/${relativePath/.ts/.md}" + outputFile="$OUTPUT_DIR/${relativePath/.ts/.mdx}" # convert zod schemas to md # pass the parent tsconfig to resolve path aliases npx zod2md --entry $schemaFile --title "$title Schema" --output "$outputFile" --tsconfig "$WORKDIR/tsconfig.json" # add schema to overview table - schemaLink="${relativePath/.ts/.md}" + schemaLink="${relativePath/.ts/.mdx}" stixType=$(dirname "$relativePath" | cut -d '/' -f 1 | tr '[:lower:]' '[:upper:]') - echo "| $title | $stixType | [Schema](/docs/$schemaLink) |" >> $OVERVIEW + echo "| $title | $stixType | [Schema](./$schemaLink) |" >> $OVERVIEW -done \ No newline at end of file +done diff --git a/package-lock.json b/package-lock.json index 034aa150..cc00e69d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,11 @@ "@eslint/js": "^9.28.0", "@semantic-release/git": "^10.0.1", "@types/uuid": "^10.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-prettier": "^5.4.1", "globals": "^15.11.0", "husky": "^9.1.7", @@ -34,6 +37,31 @@ "zod2md": "^0.2.2" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -49,6 +77,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", @@ -59,6 +97,46 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1081,6 +1159,16 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", @@ -1169,6 +1257,226 @@ "node": ">= 8" } }, + "node_modules/@npmcli/config": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.3.4.tgz", + "integrity": "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/config/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -2269,6 +2577,16 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/concat-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-2.0.3.tgz", + "integrity": "sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -2279,6 +2597,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -2293,6 +2621,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/is-empty": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/is-empty/-/is-empty-1.2.3.tgz", + "integrity": "sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2300,6 +2655,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", @@ -2317,6 +2689,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/supports-color": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -2581,7 +2967,41 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitest/expect": { + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", @@ -2696,6 +3116,16 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2860,6 +3290,36 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", + "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.29", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2877,6 +3337,17 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2922,6 +3393,13 @@ "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -2971,6 +3449,17 @@ "node": ">=6" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -3011,6 +3500,50 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -3037,6 +3570,22 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -3286,6 +3835,37 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/confbox": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", @@ -3555,6 +4135,20 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3591,6 +4185,30 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3853,6 +4471,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4053,6 +4678,73 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-mdx/-/eslint-mdx-3.6.2.tgz", + "integrity": "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "espree": "^9.6.1 || ^10.4.0", + "estree-util-visit": "^2.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "unified-engine": "^11.2.2", + "unist-util-visit": "^5.0.0", + "uvu": "^0.5.6", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0", + "remark-lint-file-extension": "*" + }, + "peerDependenciesMeta": { + "remark-lint-file-extension": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mdx": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mdx/-/eslint-plugin-mdx-3.6.2.tgz", + "integrity": "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-mdx": "^3.6.2", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "remark-mdx": "^3.1.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "synckit": "^0.11.8", + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", @@ -4313,6 +5005,32 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -4367,6 +5085,13 @@ "node": ">=12.0.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-content-type-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", @@ -5103,6 +5828,13 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5286,6 +6018,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5293,6 +6051,24 @@ "dev": true, "license": "MIT" }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5326,6 +6102,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5429,6 +6216,71 @@ "node": "^18.17 || >=20.6.1" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -5580,6 +6432,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5644,6 +6506,21 @@ "node": ">=4" } }, + "node_modules/load-plugin": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.3.tgz", + "integrity": "sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@npmcli/config": "^8.0.0", + "import-meta-resolve": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", @@ -5782,6 +6659,17 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loupe": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", @@ -5806,6 +6694,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5853,39 +6769,820 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.8", @@ -5994,6 +7691,16 @@ "ufo": "^1.5.4" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6069,6 +7776,22 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -6271,6 +7994,74 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -8974,6 +10765,33 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9350,6 +11168,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9357,6 +11185,27 @@ "dev": true, "license": "MIT" }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -9434,6 +11283,30 @@ "node": ">=0.10.0" } }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -9533,6 +11406,54 @@ "node": ">=14" } }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9563,6 +11484,16 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9638,6 +11569,19 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -10270,6 +12214,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10511,6 +12470,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -10716,6 +12716,17 @@ "tree-kill": "cli.js" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -10879,6 +12890,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -10967,6 +12985,140 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-11.2.2.tgz", + "integrity": "sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/concat-stream": "^2.0.0", + "@types/debug": "^4.0.0", + "@types/is-empty": "^1.0.0", + "@types/node": "^22.0.0", + "@types/unist": "^3.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.0.0", + "extend": "^3.0.0", + "glob": "^10.0.0", + "ignore": "^6.0.0", + "is-empty": "^1.0.0", + "is-plain-obj": "^4.0.0", + "load-plugin": "^6.0.0", + "parse-json": "^7.0.0", + "trough": "^2.0.0", + "unist-util-inspect": "^8.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-reporter": "^8.0.0", + "vfile-statistics": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified-engine/node_modules/@types/node": { + "version": "22.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", + "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/unified-engine/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/unified-engine/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unified-engine/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/unified-engine/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified-engine/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified-engine/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -10983,6 +13135,93 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-inspect": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-8.1.0.tgz", + "integrity": "sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -11040,6 +13279,35 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -11058,6 +13326,151 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-8.1.1.tgz", + "integrity": "sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/supports-color": "^8.0.0", + "string-width": "^6.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0", + "vfile-sort": "^4.0.0", + "vfile-statistics": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/vfile-sort": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-4.0.0.tgz", + "integrity": "sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-3.0.0.tgz", + "integrity": "sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", @@ -11277,6 +13690,13 @@ "dev": true, "license": "MIT" }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -11460,6 +13880,19 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11572,6 +14005,17 @@ "engines": { "node": ">=18" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 5d280884..51b26bd2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "build": "npx tsup --dts --format esm,cjs src", "prepublishOnly": "rm -rf dist && npm run build", "test": "vitest --run", + "test:coverage": "vitest --run --coverage", "test:interactive": "vitest", "export": "npm pack", "clean": "rm -rf test/**/*.js test/**/*.js.map test/**/*.d.ts test/**/*.ts.map src/**/*.js src/**/*.js.map src/**/*.d.ts examples/**/*.js examples/**/*.js.map dist", @@ -41,35 +42,13 @@ "lint:fix": "npm run lint -- --fix", "prettier": "npx prettier src --check", "prettier:fix": "npm run prettier -- --write", - "format": "npm run prettier:fix && npm run lint:fix", - "validate-bundles": "npx tsx scripts/validate-prod-stix21-bundles.ts", - "validate-local-bundle": "npx tsx scripts/validate-local-stix21-bundle.ts" + "format": "npm run prettier:fix && npm run lint:fix" }, "contributors": [ { - "name": "Sean Sica", - "email": "ssica@mitre.org", - "url": "https://github.com/seansica" - }, - { - "name": "Charissa Miller", - "email": "clmiller@mitre.org", - "url": "https://github.com/clemiller" - }, - { - "name": "Anjali Pare", - "email": "apare@mitre.org", - "url": "https://github.com/adpare" - }, - { - "name": "Erin Hall", - "email": "halle@mitre.org", - "url": "https://github.com/erinehall" - }, - { - "name": "Jared Ondricek", - "email": "jondricek@mitre.org", - "url": "https://github.com/jondricek" + "name": "MITRE ATT&CK", + "email": "attack@mitre.org", + "url": "https://github.com/mitre-attack" } ], "files": [ @@ -92,8 +71,11 @@ "@eslint/js": "^9.28.0", "@semantic-release/git": "^10.0.1", "@types/uuid": "^10.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.28.0", "eslint-config-prettier": "^9.1.0", + "eslint-mdx": "^3.6.2", + "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-prettier": "^5.4.1", "globals": "^15.11.0", "husky": "^9.1.7", diff --git a/src/data-sources/index.ts b/src/data-sources/index.ts index 2b892937..a7eea2fb 100644 --- a/src/data-sources/index.ts +++ b/src/data-sources/index.ts @@ -1 +1,2 @@ export * from './data-source-registration.js'; +export * from './fetch-attack-versions.js'; diff --git a/src/main.ts b/src/main.ts index 6079afb2..0b63856c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,9 +2,8 @@ import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import { - extensibleStixBundleSchema, + stixBundleSchema, type AttackObject, - type AttackObjects, type StixBundle, } from './schemas/sdo/stix-bundle.schema.js'; @@ -202,11 +201,10 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO const validObjects: AttackObject[] = []; // Validate the bundle's top-level properties - const baseBundleValidationResults = extensibleStixBundleSchema + const baseBundleValidationResults = stixBundleSchema .pick({ id: true, type: true, - spec_version: true, }) .loose() // <--- required to let `objects` pass-through without validation (otherwise it gets dropped and the ADM loads an empty list) .safeParse(rawData); @@ -225,7 +223,7 @@ function parseStixBundle(rawData: StixBundle, parsingMode: ParsingMode): AttackO } // Now process each object individually - const objects = rawData.objects as AttackObjects[]; + const objects = rawData.objects as AttackObject[]; for (let index = 0; index < objects.length; index++) { const obj = objects[index] as AttackObject; let objParseResult; diff --git a/src/refinements/index.ts b/src/refinements/index.ts index d5f01713..ffa25ae8 100644 --- a/src/refinements/index.ts +++ b/src/refinements/index.ts @@ -64,7 +64,7 @@ export function createFirstAliasRefinement() { * @example * ```typescript * const validateFirstXMitreAlias = createFirstXMitreAliasRefinement(); - * const schema = extensibleSchema.superRefine(validateFirstXMitreAlias); + * const schema = baseSchema.superRefine(validateFirstXMitreAlias); * ``` */ export function createFirstXMitreAliasRefinement() { @@ -168,7 +168,7 @@ export function createCitationsRefinement() { * @example * ```typescript * const validateFirstBundleObject = createFirstBundleObjectRefinement(); - * const schema = extensibleStixBundleSchema.superRefine(validateFirstBundleObject); + * const schema = stixBundleSchema.superRefine(validateFirstBundleObject); * ``` */ export function createFirstBundleObjectRefinement() { diff --git a/src/schemas/common/stix-spec-version.ts b/src/schemas/common/stix-spec-version.ts index ce047634..8263a876 100644 --- a/src/schemas/common/stix-spec-version.ts +++ b/src/schemas/common/stix-spec-version.ts @@ -7,8 +7,6 @@ const specVersionDescription = [ 'Since SCOs are now top-level objects in STIX 2.1, the default value for SCOs is 2.1.', ].join(' '); -export const stixSpecVersionSchema = z - .enum(['2.0', '2.1']) - .meta({ description: specVersionDescription }); +export const stixSpecVersionSchema = z.literal('2.1').meta({ description: specVersionDescription }); export type StixSpecVersion = z.infer; diff --git a/src/schemas/sdo/analytic.schema.ts b/src/schemas/sdo/analytic.schema.ts index 331f44eb..172d69e9 100644 --- a/src/schemas/sdo/analytic.schema.ts +++ b/src/schemas/sdo/analytic.schema.ts @@ -106,7 +106,7 @@ export type MutableElements = z.infer; // ///////////////////////////////////// -export const extensibleAnalyticSchema = attackBaseDomainObjectSchema +export const analyticSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-analytic'), @@ -132,6 +132,4 @@ export const extensibleAnalyticSchema = attackBaseDomainObjectSchema }) .strict(); -export const analyticSchema = extensibleAnalyticSchema; - -export type Analytic = z.infer; +export type Analytic = z.infer; diff --git a/src/schemas/sdo/asset.schema.ts b/src/schemas/sdo/asset.schema.ts index f7a7840a..57896a4c 100644 --- a/src/schemas/sdo/asset.schema.ts +++ b/src/schemas/sdo/asset.schema.ts @@ -1,14 +1,14 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, - descriptionSchema, - xMitrePlatformsSchema, - xMitreDomainsSchema, + createAttackExternalReferencesSchema, createStixIdValidator, + createStixTypeValidator, + descriptionSchema, xMitreContributorsSchema, + xMitreDomainsSchema, xMitreModifiedByRefSchema, - createAttackExternalReferencesSchema, - createStixTypeValidator, + xMitrePlatformsSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -78,7 +78,7 @@ export type RelatedAssets = z.infer; // ///////////////////////////////////// -export const extensibleAssetSchema = attackBaseDomainObjectSchema +export const assetSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-asset'), @@ -107,7 +107,4 @@ export const extensibleAssetSchema = attackBaseDomainObjectSchema }) .strict(); -// No refinements currently exist on assets, so just export an alias -export const assetSchema = extensibleAssetSchema; - -export type Asset = z.infer; +export type Asset = z.infer; diff --git a/src/schemas/sdo/campaign.schema.ts b/src/schemas/sdo/campaign.schema.ts index 928710b5..1a0051b2 100644 --- a/src/schemas/sdo/campaign.schema.ts +++ b/src/schemas/sdo/campaign.schema.ts @@ -1,17 +1,17 @@ +import { createCitationsRefinement, createFirstAliasRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; import { - stixTimestampSchema, - descriptionSchema, - xMitreDomainsSchema, - createStixIdValidator, aliasesSchema, createAttackExternalReferencesSchema, - xMitreModifiedByRefSchema, - xMitreContributorsSchema, + createStixIdValidator, createStixTypeValidator, + descriptionSchema, + stixTimestampSchema, + xMitreContributorsSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, } from '../common/index.js'; -import { createFirstAliasRefinement, createCitationsRefinement } from '@/refinements/index.js'; ///////////////////////////////////// // @@ -83,7 +83,7 @@ export type XMitreLastSeenCitation = z.infer { - createFirstAliasRefinement()(ctx); - createCitationsRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createCitationsRefinement()(ctx); + }); -// Define the type for Campaign -export type Campaign = z.infer; +export type Campaign = z.infer; diff --git a/src/schemas/sdo/collection.schema.ts b/src/schemas/sdo/collection.schema.ts index 6422f2a7..e44f3420 100644 --- a/src/schemas/sdo/collection.schema.ts +++ b/src/schemas/sdo/collection.schema.ts @@ -40,7 +40,7 @@ export type ObjectVersionReference = z.infer; +export type Collection = z.infer; diff --git a/src/schemas/sdo/data-component.schema.ts b/src/schemas/sdo/data-component.schema.ts index da1e8795..b725ca0b 100644 --- a/src/schemas/sdo/data-component.schema.ts +++ b/src/schemas/sdo/data-component.schema.ts @@ -71,7 +71,7 @@ export type XMitreLogSources = z.infer; // ///////////////////////////////////// -export const extensibleDataComponentSchema = attackBaseDomainObjectSchema +export const dataComponentSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-data-component'), @@ -102,7 +102,4 @@ export const extensibleDataComponentSchema = attackBaseDomainObjectSchema }) .strict(); -// No refinements currently exist on data components, so just export an alias -export const dataComponentSchema = extensibleDataComponentSchema; - -export type DataComponent = z.infer; +export type DataComponent = z.infer; diff --git a/src/schemas/sdo/data-source.schema.ts b/src/schemas/sdo/data-source.schema.ts index 543c8360..64f0474f 100644 --- a/src/schemas/sdo/data-source.schema.ts +++ b/src/schemas/sdo/data-source.schema.ts @@ -1,15 +1,15 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixIdValidator } from '../common/stix-identifier.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { - xMitrePlatformsSchema, - xMitreDomainsSchema, + createAttackExternalReferencesSchema, descriptionSchema, - xMitreModifiedByRefSchema, xMitreContributorsSchema, - createAttackExternalReferencesSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, + xMitrePlatformsSchema, } from '../common/index.js'; +import { createStixIdValidator } from '../common/stix-identifier.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -45,7 +45,7 @@ export type XMitreCollectionLayers = z.infer; +export type DataSource = z.infer; diff --git a/src/schemas/sdo/detection-strategy.schema.ts b/src/schemas/sdo/detection-strategy.schema.ts index 4953c9db..3bd633ef 100644 --- a/src/schemas/sdo/detection-strategy.schema.ts +++ b/src/schemas/sdo/detection-strategy.schema.ts @@ -1,13 +1,13 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixIdValidator } from '../common/stix-identifier.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { xMitreContributorsSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/common-properties.js'; import { createAttackExternalReferencesSchema } from '../common/misc.js'; +import { createStixIdValidator } from '../common/stix-identifier.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -15,7 +15,7 @@ import { createAttackExternalReferencesSchema } from '../common/misc.js'; // ///////////////////////////////////// -export const extensibleDetectionStrategySchema = attackBaseDomainObjectSchema +export const detectionStrategySchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-detection-strategy'), @@ -40,6 +40,4 @@ export const extensibleDetectionStrategySchema = attackBaseDomainObjectSchema 'The detection logic and patterns used to identify malicious activities based on the collected data.', }); -export const detectionStrategySchema = extensibleDetectionStrategySchema; - export type DetectionStrategy = z.infer; diff --git a/src/schemas/sdo/group.schema.ts b/src/schemas/sdo/group.schema.ts index af6914b9..4d6d04fb 100644 --- a/src/schemas/sdo/group.schema.ts +++ b/src/schemas/sdo/group.schema.ts @@ -1,11 +1,11 @@ -import { z } from 'zod/v4'; +import { createFirstAliasRefinement } from '@/refinements/index.js'; import { attackBaseDomainObjectSchema } from '@/schemas/common/attack-base-object.js'; import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; -import { createFirstAliasRefinement } from '@/refinements/index.js'; +import { z } from 'zod/v4'; import { aliasesSchema, - createStixIdValidator, createAttackExternalReferencesSchema, + createStixIdValidator, stixTimestampSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, @@ -13,7 +13,7 @@ import { import { AttackMotivationOV, AttackResourceLevelOV } from '../common/open-vocabulary.js'; // Group Schema -export const extensibleGroupSchema = attackBaseDomainObjectSchema +export const groupSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('intrusion-set'), @@ -68,12 +68,9 @@ export const extensibleGroupSchema = attackBaseDomainObjectSchema description: 'The secondary reasons, motivations, or purposes behind this Intrusion Set', }), }) - .strict(); - -export const groupSchema = extensibleGroupSchema.check((ctx) => { - // validate that when aliases are present, the first alias must match the object's name - createFirstAliasRefinement()(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + }); -// Define the TypeScript type -export type Group = z.infer; +export type Group = z.infer; diff --git a/src/schemas/sdo/identity.schema.ts b/src/schemas/sdo/identity.schema.ts index 67d5da03..6cf499fd 100644 --- a/src/schemas/sdo/identity.schema.ts +++ b/src/schemas/sdo/identity.schema.ts @@ -1,9 +1,9 @@ -import { z } from 'zod/v4'; -import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; -import { createStixIdValidator } from '@/schemas/common/stix-identifier.js'; -import { objectMarkingRefsSchema } from '@/schemas/common/common-properties.js'; import { attackBaseDomainObjectSchema } from '@/schemas/common/attack-base-object.js'; +import { objectMarkingRefsSchema } from '@/schemas/common/common-properties.js'; import { IdentityClassOV, IndustrySectorOV } from '@/schemas/common/open-vocabulary.js'; +import { createStixIdValidator } from '@/schemas/common/stix-identifier.js'; +import { createStixTypeValidator } from '@/schemas/common/stix-type.js'; +import { z } from 'zod/v4'; ///////////////////////////////////// // @@ -11,7 +11,7 @@ import { IdentityClassOV, IndustrySectorOV } from '@/schemas/common/open-vocabul // ///////////////////////////////////// -export const extensibleIdentitySchema = attackBaseDomainObjectSchema +export const identitySchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('identity'), @@ -66,8 +66,4 @@ export const extensibleIdentitySchema = attackBaseDomainObjectSchema }) .strict(); -// Alias because identities currently don't have any refinements -export const identitySchema = extensibleIdentitySchema; - -// Define the type for Identity -export type Identity = z.infer; +export type Identity = z.infer; diff --git a/src/schemas/sdo/index.ts b/src/schemas/sdo/index.ts index 288e20d6..f8a611f5 100644 --- a/src/schemas/sdo/index.ts +++ b/src/schemas/sdo/index.ts @@ -1,8 +1,7 @@ -export { analyticSchema, extensibleAnalyticSchema, type Analytic } from './analytic.schema.js'; +export { analyticSchema, type Analytic } from './analytic.schema.js'; export { assetSchema, - extensibleAssetSchema, relatedAssetSchema, relatedAssetsSchema, xMitreSectorsSchema, @@ -14,7 +13,6 @@ export { export { campaignSchema, - extensibleCampaignSchema, xMitreFirstSeenCitationSchema, xMitreLastSeenCitationSchema, type Campaign, @@ -24,7 +22,6 @@ export { export { collectionSchema, - extensibleCollectionSchema, objectVersionReferenceSchema, type Collection, type ObjectVersionReference, @@ -32,7 +29,6 @@ export { export { dataComponentSchema, - extensibleDataComponentSchema, xMitreDataSourceRefSchema, xMitreLogSourcesSchema, type DataComponent, @@ -40,44 +36,33 @@ export { type XMitreLogSources, } from './data-component.schema.js'; -export { - detectionStrategySchema, - extensibleDetectionStrategySchema, - type DetectionStrategy, -} from './detection-strategy.schema.js'; +export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js'; -export { extensibleGroupSchema, groupSchema, type Group } from './group.schema.js'; +export { groupSchema, type Group } from './group.schema.js'; -export { extensibleIdentitySchema, identitySchema, type Identity } from './identity.schema.js'; +export { identitySchema, type Identity } from './identity.schema.js'; export { dataSourceSchema, - extensibleDataSourceSchema, xMitreCollectionLayersSchema, type DataSource, type XMitreCollectionLayers, } from './data-source.schema.js'; -export { extensibleMalwareSchema, malwareSchema, type Malware } from './malware.schema.js'; +export { malwareSchema, type Malware } from './malware.schema.js'; export { - extensibleMatrixSchema, matrixSchema, xMitreTacticRefsSchema, type Matrix, type XMitreTacticRefs, } from './matrix.schema.js'; -export { - extensibleMitigationSchema, - mitigationSchema, - type Mitigation, -} from './mitigation.schema.js'; +export { mitigationSchema, type Mitigation } from './mitigation.schema.js'; -export { extensibleSoftwareSchema, softwareSchema, type Software } from './software.schema.js'; +export { softwareSchema, type Software } from './software.schema.js'; export { - extensibleTacticSchema, tacticSchema, xMitreShortNameSchema, type Tactic, @@ -85,7 +70,6 @@ export { } from './tactic.schema.js'; export { - extensibleTechniqueSchema, techniqueSchema, xMitreDataSourceSchema, xMitreDataSourcesSchema, @@ -114,11 +98,10 @@ export { type XMitreTacticType, } from './technique.schema.js'; -export { extensibleToolSchema, toolSchema, type Tool } from './tool.schema.js'; +export { toolSchema, type Tool } from './tool.schema.js'; export { attackObjectsSchema, - extensibleStixBundleSchema, stixBundleSchema, type AttackObject, type AttackObjects, diff --git a/src/schemas/sdo/malware.schema.ts b/src/schemas/sdo/malware.schema.ts index 9111bda0..3fe7d662 100644 --- a/src/schemas/sdo/malware.schema.ts +++ b/src/schemas/sdo/malware.schema.ts @@ -1,6 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { createStixTypeValidator } from '../common/stix-type.js'; -import { softwareSchema } from './software.schema.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -9,15 +11,13 @@ import { stixTimestampSchema, } from '../common/index.js'; import { - MalwareCapabilityOV, - ProcessorArchitectureOV, ImplementationLanguageOV, + MalwareCapabilityOV, MalwareTypeOV, + ProcessorArchitectureOV, } from '../common/open-vocabulary.js'; -import { - createFirstAliasRefinement, - createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; +import { softwareSchema } from './software.schema.js'; ///////////////////////////////////// // @@ -39,7 +39,7 @@ export const stixArtifactType = createStixIdValidator('artifact').meta({ // ///////////////////////////////////// -export const extensibleMalwareSchema = softwareSchema +export const malwareSchema = softwareSchema .extend({ id: createStixIdValidator('malware'), @@ -120,17 +120,10 @@ export const extensibleMalwareSchema = softwareSchema 'The sample_refs property specifies a list of identifiers of the SCO file or artifact objects associated with this malware instance(s) or family.', }), }) - .strict(); - -// Create refinement instances -const validateFirstXMitreAlias = createFirstXMitreAliasRefinement(); -const validateFirstAlias = createFirstAliasRefinement(); - -// Apply a single refinement that combines both refinements -export const malwareSchema = extensibleMalwareSchema.check((ctx) => { - validateFirstAlias(ctx); - validateFirstXMitreAlias(ctx); -}); + .strict() + .check((ctx) => { + createFirstAliasRefinement()(ctx); + createFirstXMitreAliasRefinement()(ctx); + }); -// Define the type for Malware -export type Malware = z.infer; +export type Malware = z.infer; diff --git a/src/schemas/sdo/matrix.schema.ts b/src/schemas/sdo/matrix.schema.ts index fb57d475..25115532 100644 --- a/src/schemas/sdo/matrix.schema.ts +++ b/src/schemas/sdo/matrix.schema.ts @@ -1,12 +1,12 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createStixIdValidator, descriptionSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -28,7 +28,7 @@ export type XMitreTacticRefs = z.infer; // ///////////////////////////////////// -export const extensibleMatrixSchema = attackBaseDomainObjectSchema +export const matrixSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-matrix'), @@ -49,7 +49,4 @@ export const extensibleMatrixSchema = attackBaseDomainObjectSchema }) .strict(); -// Alias unless/until matrices require at least one refinement -export const matrixSchema = extensibleMatrixSchema; - -export type Matrix = z.infer; +export type Matrix = z.infer; diff --git a/src/schemas/sdo/mitigation.schema.ts b/src/schemas/sdo/mitigation.schema.ts index d4ea6c74..347d7852 100644 --- a/src/schemas/sdo/mitigation.schema.ts +++ b/src/schemas/sdo/mitigation.schema.ts @@ -1,6 +1,5 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -11,6 +10,7 @@ import { xMitreDomainsSchema, xMitreModifiedByRefSchema, } from '../common/index.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; ///////////////////////////////////// // @@ -18,7 +18,7 @@ import { // ///////////////////////////////////// -export const extensibleMitigationSchema = attackBaseDomainObjectSchema +export const mitigationSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('course-of-action'), @@ -47,8 +47,4 @@ export const extensibleMitigationSchema = attackBaseDomainObjectSchema }) .strict(); -// Alias unless/until mitigations require at least one refinement -export const mitigationSchema = extensibleMitigationSchema; - -// Define the type for Mitigation -export type Mitigation = z.infer; +export type Mitigation = z.infer; diff --git a/src/schemas/sdo/software.schema.ts b/src/schemas/sdo/software.schema.ts index e551f3b9..ccaa2d60 100644 --- a/src/schemas/sdo/software.schema.ts +++ b/src/schemas/sdo/software.schema.ts @@ -1,15 +1,15 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema } from '../common/attack-base-object.js'; import { + aliasesSchema, + createMultiStixTypeValidator, descriptionSchema, - xMitrePlatformsSchema, - stixCreatedByRefSchema, + externalReferencesSchema, objectMarkingRefsSchema, + stixCreatedByRefSchema, xMitreDomainsSchema, - aliasesSchema, xMitreModifiedByRefSchema, - externalReferencesSchema, - createMultiStixTypeValidator, + xMitrePlatformsSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -18,7 +18,7 @@ import { // ///////////////////////////////////// -export const extensibleSoftwareSchema = attackBaseDomainObjectSchema.extend({ +export const softwareSchema = attackBaseDomainObjectSchema.extend({ type: createMultiStixTypeValidator(['malware', 'tool']), created_by_ref: stixCreatedByRefSchema.meta({ @@ -52,8 +52,4 @@ export const extensibleSoftwareSchema = attackBaseDomainObjectSchema.extend({ .meta({ description: 'Alternative names used to identify this software.' }), }); -// Alias unless/until software requires at least one refinement -export const softwareSchema = extensibleSoftwareSchema; - -// Define the type for Software -export type Software = z.infer; +export type Software = z.infer; diff --git a/src/schemas/sdo/stix-bundle.schema.ts b/src/schemas/sdo/stix-bundle.schema.ts index 1d6b7a76..567db6c3 100644 --- a/src/schemas/sdo/stix-bundle.schema.ts +++ b/src/schemas/sdo/stix-bundle.schema.ts @@ -1,28 +1,27 @@ +import { createFirstBundleObjectRefinement } from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { createStixTypeValidator } from '../common/stix-type.js'; import { createStixIdValidator } from '../common/stix-identifier.js'; -import { type Malware, malwareSchema } from './malware.schema.js'; +import { createStixTypeValidator } from '../common/stix-type.js'; +import { + markingDefinitionSchema, + type MarkingDefinition, +} from '../smo/marking-definition.schema.js'; +import { relationshipSchema, type Relationship } from '../sro/relationship.schema.js'; +import { type Analytic, analyticSchema } from './analytic.schema.js'; import { type Asset, assetSchema } from './asset.schema.js'; import { type Campaign, campaignSchema } from './campaign.schema.js'; +import { type Collection, collectionSchema } from './collection.schema.js'; import { type DataComponent, dataComponentSchema } from './data-component.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 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 @@ -172,27 +171,39 @@ export type AttackObjects = z.infer; // ///////////////////////////////////// -export const extensibleStixBundleSchema = z +export const stixBundleSchema = z .object({ id: createStixIdValidator('bundle'), - type: createStixTypeValidator('bundle'), - - spec_version: z - .literal(stixSpecVersionSchema.enum['2.1'] as StixSpecVersion) - .meta({ description: 'Only STIX 2.1 specification version is allowed' }), - - objects: attackObjectsSchema, + objects: z + .array( + z.discriminatedUnion('type', [ + collectionSchema, + analyticSchema, + assetSchema, + campaignSchema, + dataComponentSchema, + dataSourceSchema, + detectionStrategySchema, + groupSchema, + identitySchema, + malwareSchema, + matrixSchema, + mitigationSchema, + tacticSchema, + techniqueSchema, + toolSchema, + ]), + ) + .min(1), }) - .strict(); - -// Create refinement instance -const validateFirstBundleObject = createFirstBundleObjectRefinement(); - -// Apply the refinement -export const stixBundleSchema = extensibleStixBundleSchema.check((ctx) => { - validateFirstBundleObject(ctx); -}); + .meta({ + description: + 'A Bundle is a collection of arbitrary STIX Objects grouped together in a single container. A Bundle does not have any semantic meaning and the objects contained within the Bundle are not considered related by virtue of being in the same Bundle. A STIX Bundle Object is not a STIX Object but makes use of the type and id Common Properties.', + }) + .strict() + .check((ctx) => { + createFirstBundleObjectRefinement()(ctx); + }); -// Define the type for StixBundle -export type StixBundle = z.infer; +export type StixBundle = z.infer; diff --git a/src/schemas/sdo/tactic.schema.ts b/src/schemas/sdo/tactic.schema.ts index 6fe81209..af338ce6 100644 --- a/src/schemas/sdo/tactic.schema.ts +++ b/src/schemas/sdo/tactic.schema.ts @@ -1,13 +1,13 @@ import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, + createAttackExternalReferencesSchema, createStixIdValidator, createStixTypeValidator, descriptionSchema, - xMitreModifiedByRefSchema, - xMitreDomainsSchema, xMitreContributorsSchema, - createAttackExternalReferencesSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, } from '../common/index.js'; ///////////////////////////////////// @@ -72,7 +72,7 @@ export type XMitreShortName = z.infer; // ///////////////////////////////////// -export const extensibleTacticSchema = attackBaseDomainObjectSchema +export const tacticSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('x-mitre-tactic'), @@ -91,13 +91,14 @@ export const extensibleTacticSchema = attackBaseDomainObjectSchema x_mitre_contributors: xMitreContributorsSchema.optional(), }) + .meta({ + description: + "Tactics represent the adversary's tactical goals during an attack and are defined by `x-mitre-tactic` objects. As custom STIX types, they extend the generic STIX Domain Object pattern.", + }) .required({ created_by_ref: true, // Optional in STIX but required in ATT&CK object_marking_refs: true, // Optional in STIX but required in ATT&CK }) .strict(); -// Alias unless/until tactics require at least one refinement -export const tacticSchema = extensibleTacticSchema; - -export type Tactic = z.infer; +export type Tactic = z.infer; diff --git a/src/schemas/sdo/technique.schema.ts b/src/schemas/sdo/technique.schema.ts index d2c3bffc..4d00e635 100644 --- a/src/schemas/sdo/technique.schema.ts +++ b/src/schemas/sdo/technique.schema.ts @@ -1,21 +1,21 @@ +import { + createAttackIdInExternalReferencesRefinement, + createEnterpriseOnlyPropertiesRefinement, + createMobileOnlyPropertiesRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; import { attackBaseDomainObjectSchema, - descriptionSchema, - xMitrePlatformsSchema, + createAttackExternalReferencesSchema, createStixIdValidator, createStixTypeValidator, - xMitreModifiedByRefSchema, - xMitreDomainsSchema, - xMitreContributorsSchema, + descriptionSchema, killChainPhaseSchema, - createAttackExternalReferencesSchema, + xMitreContributorsSchema, + xMitreDomainsSchema, + xMitreModifiedByRefSchema, + xMitrePlatformsSchema, } from '../common/index.js'; -import { - createAttackIdInExternalReferencesRefinement, - createEnterpriseOnlyPropertiesRefinement, - createMobileOnlyPropertiesRefinement, -} from '@/refinements/index.js'; ///////////////////////////////////// // @@ -325,7 +325,7 @@ export type XMitreDetection = z.infer; // ///////////////////////////////////// -export const extensibleTechniqueSchema = attackBaseDomainObjectSchema +export const techniqueSchema = attackBaseDomainObjectSchema .extend({ id: createStixIdValidator('attack-pattern'), @@ -368,16 +368,14 @@ export const extensibleTechniqueSchema = attackBaseDomainObjectSchema x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional(), }) - .strict(); - -// Apply the refinements for techniques -export const techniqueSchema = extensibleTechniqueSchema.check((ctx) => { - // Validates that the first external reference is a valid ATT&CK ID - createAttackIdInExternalReferencesRefinement()(ctx); - // Validates that the technique only contains properties permissible by the target tactic in Enterprise - createEnterpriseOnlyPropertiesRefinement()(ctx); - // Validates that the technique only contains properties permissible in Mobile (if the technique belongs to Mobile) - createMobileOnlyPropertiesRefinement()(ctx); -}); - -export type Technique = z.infer; + .meta({ + description: + 'Techniques describe specific methods adversaries use to achieve tactical objectives and are represented as [attack-pattern](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230921) objects following the STIX 2.1 specification.', + }) + .strict() + .check((ctx) => { + createAttackIdInExternalReferencesRefinement()(ctx); + createEnterpriseOnlyPropertiesRefinement()(ctx); + createMobileOnlyPropertiesRefinement()(ctx); + }); +export type Technique = z.infer; diff --git a/src/schemas/sdo/tool.schema.ts b/src/schemas/sdo/tool.schema.ts index 05569db8..c0e5c143 100644 --- a/src/schemas/sdo/tool.schema.ts +++ b/src/schemas/sdo/tool.schema.ts @@ -1,5 +1,8 @@ +import { + createFirstAliasRefinement, + createFirstXMitreAliasRefinement, +} from '@/refinements/index.js'; import { z } from 'zod/v4'; -import { softwareSchema } from './software.schema.js'; import { createAttackExternalReferencesSchema, createOldMitreAttackIdSchema, @@ -8,10 +11,7 @@ import { killChainPhaseSchema, } from '../common/index.js'; import { ToolTypeOV } from '../common/open-vocabulary.js'; -import { - createFirstAliasRefinement, - createFirstXMitreAliasRefinement, -} from '@/refinements/index.js'; +import { softwareSchema } from './software.schema.js'; ///////////////////////////////////// // @@ -19,7 +19,7 @@ import { // ///////////////////////////////////// -export const extensibleToolSchema = softwareSchema +export const toolSchema = softwareSchema .extend({ id: createStixIdValidator('tool'), @@ -47,12 +47,10 @@ export const extensibleToolSchema = softwareSchema x_mitre_old_attack_id: createOldMitreAttackIdSchema('tool').optional(), }) - .strict(); - -// Apply a single refinement that combines both refinements -export const toolSchema = extensibleToolSchema.check((ctx) => { - createFirstXMitreAliasRefinement()(ctx); - createFirstAliasRefinement()(ctx); -}); -// Define the type for Tool -export type Tool = z.infer; + .strict() + .check((ctx) => { + createFirstXMitreAliasRefinement()(ctx); + createFirstAliasRefinement()(ctx); + }); + +export type Tool = z.infer; diff --git a/src/schemas/sro/relationship.schema.ts b/src/schemas/sro/relationship.schema.ts index 6fda0d4b..4c59035c 100644 --- a/src/schemas/sro/relationship.schema.ts +++ b/src/schemas/sro/relationship.schema.ts @@ -279,7 +279,7 @@ export function createRelationshipValidationRefinement() { // ///////////////////////////////////// -export const extensibleRelationshipSchema = attackBaseRelationshipObjectSchema +export const relationshipSchema = attackBaseRelationshipObjectSchema .extend({ id: createStixIdValidator('relationship'), @@ -300,6 +300,9 @@ export const extensibleRelationshipSchema = attackBaseRelationshipObjectSchema x_mitre_version: true, }) .strict() + .check((ctx) => { + createRelationshipValidationRefinement()(ctx); + }) .transform((data) => { // Check for deprecated pattern const [sourceType] = data.source_ref.split('--') as [StixType]; @@ -315,8 +318,4 @@ export const extensibleRelationshipSchema = attackBaseRelationshipObjectSchema return data; }); -export const relationshipSchema = extensibleRelationshipSchema.check((ctx) => { - createRelationshipValidationRefinement()(ctx); -}); - -export type Relationship = z.infer; +export type Relationship = z.infer; diff --git a/test/documentation/README.test.ts b/test/documentation/README.test.ts new file mode 100644 index 00000000..c991056e --- /dev/null +++ b/test/documentation/README.test.ts @@ -0,0 +1,202 @@ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { tacticSchema } from '../../src/schemas/index.js'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('README.md Code Examples', () => { + describe('Installation Examples', () => { + it('should validate npm install command format', () => { + // Maps to: README.md - "Installation" section + // Code block: ```bash npm install @mitre-attack/attack-data-model + const packageName = '@mitre-attack/attack-data-model'; + expect(packageName).toMatch(/^@[a-z-]+\/[a-z-]+$/); + }); + + it('should validate version compatibility patterns', () => { + // Maps to: README.md - "Installing Specific Versions" section + // Code blocks: ```bash npm install @mitre-attack/attack-data-model@4.0.0 + const versions = ['4.0.0', '^4.0.0', '~4.0.0', '5.x', '17.1']; + versions.forEach((version) => { + expect(version).toMatch(/^[~^]?\d+(\.\d+)?(\.\d+)?$|\d+\.x$/); + }); + }); + + it('should demonstrate version checking from collection object', () => { + // Maps to: README.md - "How to Check Your ATT&CK Version" section + // Code block: ```json { "type": "x-mitre-collection", "x_mitre_attack_spec_version": "3.2.0" } + const collections = globalThis.attackData.objectsByType['x-mitre-collection'] || []; + + if (collections.length > 0) { + const collection = collections[0]; + expect(collection.x_mitre_attack_spec_version).toBeDefined(); + expect(collection.name).toBeDefined(); + } + }); + }); + + describe('Recommended Approach Example', () => { + it('should work with the loading example from README', () => { + // Maps to: README.md - "Recommended Approach" section + // Code block: ```javascript const dataSource = new DataSourceRegistration({ source: 'attack', ... }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'strict', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('17.1'); + expect(dataSource.options.parsingMode).toBe('strict'); + }); + }); + + describe('Basic Usage Examples', () => { + it('should work with the async function example', () => { + // Maps to: README.md - "Basic Usage" section + // Code block: ```typescript const dataSource = new DataSourceRegistration({ ..., parsingMode: 'relaxed' }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('15.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + + it('should validate that real ATT&CK objects have documented structure', () => { + // Maps to: README.md - "Basic Usage" section + // Code patterns: accessing techniques, tactics, technique properties + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + const tactics = globalThis.attackData.objectsByType['x-mitre-tactic'] || []; + + expect(techniques.length).toBeGreaterThan(100); + expect(tactics.length).toBeGreaterThan(5); + + if (techniques.length > 0) { + const technique = techniques[0]; + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + expect(technique.external_references[0]?.external_id).toBeDefined(); + } + }); + + it('should demonstrate subtechnique patterns', () => { + // Maps to: README.md - "Basic Usage" section + // Code pattern: if (technique.x_mitre_is_subtechnique) { console.log(technique.getParentTechnique()); } + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + const parentTechnique = techniques.find( + (t) => 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === false, + ); + + if (parentTechnique) { + expect(parentTechnique.x_mitre_is_subtechnique).toBe(false); + } + + const subtechnique = techniques.find( + (t) => 'x_mitre_is_subtechnique' in t && t.x_mitre_is_subtechnique === true, + ); + + if (subtechnique) { + expect(subtechnique.x_mitre_is_subtechnique).toBe(true); + } + }); + + it('should demonstrate accessing attack objects by type', () => { + // Maps to: README.md - "Basic Usage" section + // Code pattern: accessing attackDataModel.techniques, attackDataModel.tactics, etc. + expect(globalThis.attackData.objectsByType['attack-pattern']).toBeDefined(); + expect(globalThis.attackData.objectsByType['x-mitre-tactic']).toBeDefined(); + expect(globalThis.attackData.objectsByType['intrusion-set']).toBeDefined(); + expect(globalThis.attackData.objectsByType['malware']).toBeDefined(); + expect(globalThis.attackData.objectsByType['tool']).toBeDefined(); + expect(globalThis.attackData.objectsByType['course-of-action']).toBeDefined(); + expect(globalThis.attackData.objectsByType['campaign']).toBeDefined(); + }); + }); + + describe('Parsing and Validating a Tactic Examples', () => { + it('should parse valid tactic as shown in README', () => { + // Maps to: README.md - "Parsing and Validating a Tactic" section + // Code block: ```typescript import { tacticSchema } from "@mitre-attack/attack-data-model"; + const validTactic = createSyntheticStixObject('x-mitre-tactic'); + + expect(() => { + const parsedTactic = tacticSchema.parse(validTactic); + expect(parsedTactic.name).toBeDefined(); + expect(parsedTactic.x_mitre_shortname).toBeDefined(); + }).not.toThrow(); + }); + + it('should validate that real tactic objects match README examples', () => { + // Maps to: README.md - "Parsing and Validating a Tactic" section + // Validates that real objects have the structure shown in examples + const tactics = globalThis.attackData.objectsByType['x-mitre-tactic'] || []; + + if (tactics.length > 0) { + const tactic = tactics[0]; + + expect(tactic.id).toBeDefined(); + expect(tactic.type).toBe('x-mitre-tactic'); + expect(tactic.name).toBeDefined(); + expect(tactic.x_mitre_shortname).toBeDefined(); + expect(tactic.external_references).toBeDefined(); + } + }); + }); + + describe('Handling Invalid Data Examples', () => { + it('should handle invalid tactic data as shown in README', () => { + // Maps to: README.md - "Handling Invalid Data" section + // Code block: ```typescript const invalidTactic = { id: "...", type: "x-mitre-tactic" }; + const invalidTactic = { + id: 'x-mitre-tactic--4ca45d45-df4d-4613-8980-bac22d278fa5', + type: 'x-mitre-tactic', + // Missing required fields like name, description, etc. + }; + + expect(() => { + tacticSchema.parse(invalidTactic); + }).toThrow(z.ZodError); + }); + }); + + describe('How It Works Examples', () => { + it('should demonstrate relationship processing patterns', () => { + // Maps to: README.md - "How It Works" section, step 5 + // Text: "automatically processes all 'relationship' objects in the dataset" + const relationships = globalThis.attackData.sros || []; + + expect(relationships.length).toBeGreaterThan(0); + + const usesRelationship = relationships.find((rel) => rel.relationship_type === 'uses'); + + if (usesRelationship) { + expect(usesRelationship.source_ref).toBeDefined(); + expect(usesRelationship.target_ref).toBeDefined(); + expect(usesRelationship.relationship_type).toBe('uses'); + } + }); + }); + + describe('AttackDataModel Constructor Examples', () => { + it('should demonstrate AttackDataModel instantiation', () => { + // Maps to: README.md - Various sections showing AttackDataModel usage + // Code pattern: new AttackDataModel(uuid, objects) constructor usage + const uuid = 'test-unique-id'; + const attackObjects: any[] = []; + const testDataModel = new AttackDataModel(uuid, attackObjects); + + expect(testDataModel.getUuid()).toBe(uuid); + expect(typeof testDataModel.getUuid()).toBe('string'); + }); + }); +}); diff --git a/test/documentation/USAGE.test.ts b/test/documentation/USAGE.test.ts new file mode 100644 index 00000000..e6acdfe0 --- /dev/null +++ b/test/documentation/USAGE.test.ts @@ -0,0 +1,245 @@ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import { createSyntheticStixObject } from '../../src/generator/index.js'; +import { tacticSchema, campaignSchema, techniqueSchema } from '../../src/schemas/index.js'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('docs/USAGE.md Code Examples', () => { + describe('Module Format Support Examples', () => { + it('should work with ESM example', () => { + // Maps to: docs/USAGE.md - "ESM Usage Example" section + // Code block: ```javascript import { AttackDataModel } from '@mitre-attack/attack-data-model'; + const uuid = 'my-unique-id'; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.getUuid()).toBe('my-unique-id'); + }); + + it('should work with CommonJS pattern', () => { + // Maps to: docs/USAGE.md - "CommonJS Usage Example" section + // Code block: ```javascript const { AttackDataModel } = require('@mitre-attack/attack-data-model'); + const uuid = 'my-unique-id'; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.getUuid()).toBe('my-unique-id'); + }); + }); + + describe('Package Structure Examples', () => { + it('should support the hierarchical export pattern', () => { + // Maps to: docs/USAGE.md - "Hierarchical Structure" section + // Code block: ```typescript export * from './classes/index.js'; + expect(tacticSchema).toBeDefined(); + expect(campaignSchema).toBeDefined(); + expect(techniqueSchema).toBeDefined(); + expect(AttackDataModel).toBeDefined(); + expect(DataSourceRegistration).toBeDefined(); + }); + }); + + describe('Using the Schemas Examples', () => { + it('should support schema import patterns', () => { + // Maps to: docs/USAGE.md - "Accessing Schemas" section + // Code block: ```typescript import { campaignSchema } from '@mitre-attack/attack-data-model'; + const validCampaign = createSyntheticStixObject('campaign'); + + expect(() => { + const parsedCampaign = campaignSchema.parse(validCampaign); + expect(parsedCampaign.name).toBeDefined(); + }).not.toThrow(); + }); + + it('should demonstrate schema extension patterns', () => { + // Maps to: docs/USAGE.md - Schema extension section + // Code block: ```typescript const myCustomCampaignSchema = campaignSchema.extend({ /* additional fields */ }); + // Note: We can't test the exact extend example due to refinement issues mentioned in docs + // Instead, we test that the base schema works as expected + const campaign = createSyntheticStixObject('campaign'); + + expect(() => { + const parsed = campaignSchema.parse(campaign); + expect(parsed).toBeDefined(); + }).not.toThrow(); + }); + + it('should demonstrate refinement functionality', () => { + // Maps to: docs/USAGE.md - "Schema refinements" section + // Text: "validating that the first reference in external_references contains a valid ATT&CK ID" + const technique = createSyntheticStixObject('attack-pattern'); + + expect(technique?.external_references).toBeDefined(); + expect(technique?.external_references?.[0]).toBeDefined(); + expect(technique?.external_references?.[0].source_name).toBe('mitre-attack'); + expect(technique?.external_references?.[0].external_id).toBeDefined(); + + expect(() => { + techniqueSchema.parse(technique); + }).not.toThrow(); + }); + }); + + describe('Validating Data Examples', () => { + it('should validate raw tactic data as shown in USAGE', () => { + // Maps to: docs/USAGE.md - "Validating Data" section + // Code block: ```typescript import { tacticSchema } from '@mitre-attack/attack-data-model'; + const validTactic = createSyntheticStixObject('x-mitre-tactic'); + + expect(() => { + const parsedTactic = tacticSchema.parse(validTactic); + expect(parsedTactic.name).toBeDefined(); + expect(parsedTactic.x_mitre_shortname).toBeDefined(); + }).not.toThrow(); + }); + }); + + describe('TypeScript Types Examples', () => { + it('should support TypeScript type patterns', () => { + // Maps to: docs/USAGE.md - "TypeScript Types" section + // Code block: ```typescript import type { Tactic } from '@mitre-attack/attack-data-model'; + // Test that type imports work correctly (validated at compile time) + expect(tacticSchema).toBeDefined(); + expect(campaignSchema).toBeDefined(); + expect(techniqueSchema).toBeDefined(); + }); + }); + + describe('Schema Hierarchy Examples', () => { + it('should support schema hierarchy as described', () => { + // Maps to: docs/USAGE.md - "Schema Hierarchy" section + // Validates the hierarchical structure: STIX base โ†’ ATT&CK base โ†’ specific objects + const validTechnique = createSyntheticStixObject('attack-pattern'); + const parsed = techniqueSchema.parse(validTechnique); + + // Should have STIX base properties + expect(parsed.id).toBeDefined(); + expect(parsed.type).toBe('attack-pattern'); + expect(parsed.spec_version).toBeDefined(); + + // Should have ATT&CK-specific properties + expect(parsed.x_mitre_domains).toBeDefined(); + expect(parsed.x_mitre_attack_spec_version).toBeDefined(); + }); + }); + + describe('AttackDataModel Class Examples', () => { + it('should demonstrate AttackDataModel basic usage', () => { + // Maps to: docs/USAGE.md - "AttackDataModel Class" section + // Code block: ```typescript const attackDataModel = new AttackDataModel(); + const uuid = 'test-uuid'; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + expect(attackDataModel.techniques).toBeDefined(); + expect(Array.isArray(attackDataModel.techniques)).toBe(true); + expect(attackDataModel.campaigns).toBeDefined(); + expect(Array.isArray(attackDataModel.campaigns)).toBe(true); + }); + }); + + describe('Initializing with Data Examples', () => { + it('should support DataSource configuration patterns', () => { + // Maps to: docs/USAGE.md - "Initializing with Data" section + // Code block: ```typescript const dataSource = new DataSource({ source: 'attack', ... }); + // Note: Using DataSourceRegistration instead of DataSource as shown in examples + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('15.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); + + describe('Relationship Mapping Examples', () => { + it('should validate campaign relationship navigation patterns', () => { + // Maps to: docs/USAGE.md - "Relationship Mapping" section + // Code block: ```typescript const techniques = campaign.getTechniques(); + // Test using real data to validate relationship patterns exist + const campaigns = globalThis.attackData.objectsByType['campaign'] || []; + + if (campaigns.length > 0) { + const campaign = campaigns[0]; + + // Properties mentioned in the relationship mapping examples + expect(campaign.name).toBeDefined(); + expect(campaign.id).toBeDefined(); + expect(campaign.type).toBe('campaign'); + } + }); + }); + + describe('Examples Section Code Blocks', () => { + it('should demonstrate technique-tactic navigation', () => { + // Maps to: docs/USAGE.md - "Examples" section + // Code block: ```typescript techniques.forEach((technique) => { const tactics = technique.getTactics(); }); + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + if (techniques.length > 0) { + const technique = techniques[0]; + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + } + }); + + it('should demonstrate software-group relationships', () => { + // Maps to: docs/USAGE.md - "Working with Software and Groups" section + // Code block: ```typescript malwareList.forEach((malware) => { const associatedGroups = malware.getAssociatedGroups(); }); + const malware = globalThis.attackData.objectsByType['malware'] || []; + + if (malware.length > 0) { + const malwareItem = malware[0]; + expect(malwareItem.name).toBeDefined(); + expect(malwareItem.type).toBe('malware'); + } + }); + + it('should demonstrate technique validation patterns', () => { + // Maps to: docs/USAGE.md - "Validating and Parsing a Technique" section + // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; + const rawTechniqueData = createSyntheticStixObject('attack-pattern'); + + expect(() => { + const technique = techniqueSchema.parse(rawTechniqueData); + expect(technique.name).toBeDefined(); + }).not.toThrow(); + }); + + it('should handle invalid technique data', () => { + // Maps to: docs/USAGE.md - "Handling Invalid Data" section + // Code block: ```typescript import { techniqueSchema } from '@mitre-attack/attack-data-model'; + const invalidTechniqueData = { + id: 'attack-pattern--1234abcd-5678-efgh-ijkl-9876mnopqrst', + type: 'attack-pattern', + // Missing required fields + }; + + expect(() => { + techniqueSchema.parse(invalidTechniqueData); + }).toThrow(z.ZodError); + }); + }); + + describe('Data Sources Examples', () => { + it('should support data source patterns mentioned in USAGE', () => { + // Maps to: docs/USAGE.md - "Data Sources" section + // Text: "Loading Data from the ATT&CK GitHub Repository" + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '15.1', + parsingMode: 'relaxed', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); +}); diff --git a/test/documentation/first-query.test.ts b/test/documentation/first-query.test.ts new file mode 100644 index 00000000..178b410f --- /dev/null +++ b/test/documentation/first-query.test.ts @@ -0,0 +1,185 @@ +import { describe, it, expect } from 'vitest'; +import { DataSourceRegistration } from '../../src/data-sources/data-source-registration.js'; +import { AttackDataModel } from '../../src/classes/attack-data-model.js'; + +describe('examples/first-query.ts Code Example', () => { + describe('DataSourceRegistration Configuration', () => { + it('should use the exact configuration from the example', () => { + // Maps to: examples/first-query.ts - lines 7-12 + // Code: const dataSource = new DataSourceRegistration({ source: 'attack', domain: 'enterprise-attack', version: '17.1', parsingMode: 'relaxed' }); + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + expect(dataSource.options.source).toBe('attack'); + expect(dataSource.options.domain).toBe('enterprise-attack'); + expect(dataSource.options.version).toBe('17.1'); + expect(dataSource.options.parsingMode).toBe('relaxed'); + }); + }); + + describe('AttackDataModel Usage Patterns', () => { + it('should validate AttackDataModel methods used in the example', () => { + // Maps to: examples/first-query.ts - lines 22-31 + // Code: const attackDataModel = loadDataModel(uuid); attackDataModel.techniques.length; attackDataModel.techniques.slice(0, 5) + const uuid = 'test-uuid'; + const attackObjects: any[] = []; + const attackDataModel = new AttackDataModel(uuid, attackObjects); + + // The example uses these properties/methods + expect(attackDataModel.techniques).toBeDefined(); + expect(Array.isArray(attackDataModel.techniques)).toBe(true); + expect(typeof attackDataModel.techniques.length).toBe('number'); + + // The example calls .slice(0, 5) on techniques array + expect(typeof attackDataModel.techniques.slice).toBe('function'); + + // Test that slice works as expected in the example + const sliced = attackDataModel.techniques.slice(0, 5); + expect(Array.isArray(sliced)).toBe(true); + expect(sliced.length).toBeLessThanOrEqual(5); + }); + }); + + describe('Technique Data Structure Validation', () => { + it('should validate that real techniques have properties accessed in the example', () => { + // Maps to: examples/first-query.ts - line 29 + // Code: technique.name and technique.external_references[0].external_id + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + if (techniques.length > 0) { + const technique = techniques[0]; + + // Properties accessed in the example + expect(technique.name).toBeDefined(); + expect(technique.external_references).toBeDefined(); + expect(Array.isArray(technique.external_references)).toBe(true); + + if (technique.external_references.length > 0) { + expect(technique.external_references[0].external_id).toBeDefined(); + + // The example formats output as: `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})` + const formattedOutput = `1. ${technique.name} (${technique.external_references[0].external_id})`; + expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); + } + } + }); + + it('should validate the example output format pattern', () => { + // Maps to: examples/first-query.ts - line 28-31 + // Code: forEach((technique, index) => { console.log(`${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`); }); + const techniques = globalThis.attackData.objectsByType['attack-pattern'] || []; + + // Test the first 5 techniques as the example does + const first5Techniques = techniques.slice(0, 5); + + first5Techniques.forEach((technique, index) => { + expect(technique.name).toBeDefined(); + expect(technique.external_references?.[0]?.external_id).toBeDefined(); + + // Validate the exact output format from the example + const formattedOutput = `${index + 1}. ${technique.name} (${technique.external_references[0].external_id})`; + expect(formattedOutput).toMatch(/^\d+\. .+ \([A-Z]\d+(\.\d+)?\)$/); + expect(formattedOutput).toContain(technique.name); + expect(formattedOutput).toContain(technique.external_references[0].external_id); + }); + }); + }); + + describe('Async Function Pattern Validation', () => { + it('should validate the async function structure used in the example', () => { + // Maps to: examples/first-query.ts - lines 3-38 + // Code: async function exploreAttackData() { try { const uuid = await registerDataSource(dataSource); } catch (error) { ... } } + + // Test that the async pattern components work + const testAsyncPattern = async () => { + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + // The example checks these properties exist + expect(dataSource.options.source).toBeDefined(); + expect(dataSource.options.domain).toBeDefined(); + expect(dataSource.options.version).toBeDefined(); + expect(dataSource.options.parsingMode).toBeDefined(); + + return dataSource; + }; + + expect(testAsyncPattern).not.toThrow(); + }); + }); + + describe('Console Output Patterns', () => { + it('should validate console output message formats from the example', () => { + // Maps to: examples/first-query.ts - lines 4, 19, 23, 26 + // Code: console.log('๐ŸŽฏ Loading ATT&CK Enterprise data...\n'); console.log(`๐Ÿ“Š Loaded ${attackDataModel.techniques.length} techniques\n`); + + const techniqueCount = globalThis.attackData.objectsByType['attack-pattern']?.length || 0; + + // Test the loading message format from the example + const loadingMessage = `Loaded ${techniqueCount} techniques`; + expect(loadingMessage).toMatch(/^Loaded \d+ techniques$/); + expect(techniqueCount).toBeGreaterThan(0); + + // Test the exploration message format + const explorationMessage = 'First 5 techniques:'; + expect(explorationMessage).toBe('First 5 techniques:'); + }); + }); + + describe('Error Handling Pattern', () => { + it('should validate error handling structure from the example', () => { + // Maps to: examples/first-query.ts - lines 32-37 + // Code: } else { console.error('โŒ Failed to register data source'); } } catch (error) { console.error('โŒ Error:', error); } + + // Test that the error handling patterns would work + const testErrorHandling = () => { + try { + // Simulate the uuid check pattern from the example + const uuid = 'test-uuid'; // In real example, this comes from registerDataSource + + if (uuid) { + expect(uuid).toBeDefined(); + expect(typeof uuid).toBe('string'); + } else { + // This would match the error case in the example + expect(uuid).toBeNull(); + } + } catch (error) { + // Error handling as shown in the example + expect(error).toBeDefined(); + } + }; + + expect(testErrorHandling).not.toThrow(); + }); + }); + + describe('Import Statement Validation', () => { + it('should validate the imports used in the example', () => { + // Maps to: examples/first-query.ts - line 1 + // Code: import { registerDataSource, loadDataModel, DataSourceRegistration } from '@mitre-attack/attack-data-model'; + + // Test that the imported classes/functions exist and are usable + expect(DataSourceRegistration).toBeDefined(); + expect(typeof DataSourceRegistration).toBe('function'); + + // Test that DataSourceRegistration can be instantiated as shown in the example + const dataSource = new DataSourceRegistration({ + source: 'attack', + domain: 'enterprise-attack', + version: '17.1', + parsingMode: 'relaxed', + }); + + expect(dataSource).toBeInstanceOf(DataSourceRegistration); + }); + }); +}); diff --git a/test/global.d.ts b/test/global.d.ts index ba5223f5..83a34bcd 100644 --- a/test/global.d.ts +++ b/test/global.d.ts @@ -2,5 +2,5 @@ import { getAttackObjects } from './utils/attack-data'; declare global { - var attackData: Awaited>; -} \ No newline at end of file + var attackData: Awaited>; +} diff --git a/test/objects/asset.test.ts b/test/objects/asset.test.ts index 53fbe8af..738254e1 100644 --- a/test/objects/asset.test.ts +++ b/test/objects/asset.test.ts @@ -1,8 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - xMitreIdentity -} from '../../src/schemas/common/index'; +import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Asset, assetSchema } from '../../src/schemas/sdo/asset.schema'; /** diff --git a/test/objects/campaign.test.ts b/test/objects/campaign.test.ts index 17e1ee96..e92391e6 100644 --- a/test/objects/campaign.test.ts +++ b/test/objects/campaign.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import type { - StixTimestamp -} from '../../src/schemas/common/index'; +import type { StixTimestamp } from '../../src/schemas/common/index'; import { type Campaign, campaignSchema } from '../../src/schemas/sdo/campaign.schema'; /** diff --git a/test/objects/data-source.test.ts b/test/objects/data-source.test.ts index 3d40cc29..07d8177d 100644 --- a/test/objects/data-source.test.ts +++ b/test/objects/data-source.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type DataSource, dataSourceSchema } from '../../src/schemas/sdo/data-source.schema'; describe('dataSourceSchema', () => { diff --git a/test/objects/detection-strategy.test.ts b/test/objects/detection-strategy.test.ts index b5442e73..17b60e19 100644 --- a/test/objects/detection-strategy.test.ts +++ b/test/objects/detection-strategy.test.ts @@ -1,10 +1,11 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { - type ExternalReferences -} from '../../src/schemas/common/index'; -import { type DetectionStrategy, detectionStrategySchema } from '../../src/schemas/sdo/detection-strategy.schema'; + type DetectionStrategy, + detectionStrategySchema, +} from '../../src/schemas/sdo/detection-strategy.schema'; describe('detectionStrategySchema', () => { const minimalDetectionStrategy = createSyntheticStixObject('x-mitre-detection-strategy'); @@ -44,7 +45,11 @@ describe('detectionStrategySchema', () => { }); describe('Field-Specific Tests', () => { - const testField = (fieldName: keyof DetectionStrategy, invalidValue: any, isRequired = true) => { + const testField = ( + fieldName: keyof DetectionStrategy, + invalidValue: any, + isRequired = true, + ) => { it(`should reject invalid values for ${fieldName}`, () => { const invalidObject = { ...minimalDetectionStrategy, [fieldName]: invalidValue }; expect(() => detectionStrategySchema.parse(invalidObject)).toThrow(); @@ -284,4 +289,4 @@ describe('detectionStrategySchema', () => { expect(() => detectionStrategySchema.parse(detectionStrategyWithVariousUUIDs)).not.toThrow(); }); }); -}); \ No newline at end of file +}); diff --git a/test/objects/group.test.ts b/test/objects/group.test.ts index 186cd71b..2cddc7d2 100644 --- a/test/objects/group.test.ts +++ b/test/objects/group.test.ts @@ -1,9 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import type { - Description -} from '../../src/schemas/common/index'; +import type { Description } from '../../src/schemas/common/index'; import { type Group, groupSchema } from '../../src/schemas/sdo/group.schema'; /** diff --git a/test/objects/malware.test.ts b/test/objects/malware.test.ts index be9861a8..d5fbffeb 100644 --- a/test/objects/malware.test.ts +++ b/test/objects/malware.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type StixTimestamp -} from '../../src/schemas/common/index'; +import { type StixTimestamp } from '../../src/schemas/common/index'; import { type Malware, malwareSchema } from '../../src/schemas/sdo/malware.schema'; describe('MalwareSchema', () => { diff --git a/test/objects/mitigation.test.ts b/test/objects/mitigation.test.ts index 040bb832..690785b5 100644 --- a/test/objects/mitigation.test.ts +++ b/test/objects/mitigation.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - type ExternalReferences -} from '../../src/schemas/common/index'; +import { type ExternalReferences } from '../../src/schemas/common/index'; import { type Mitigation, mitigationSchema } from '../../src/schemas/sdo/mitigation.schema'; describe('MitigationSchema', () => { diff --git a/test/objects/relationship.test.ts b/test/objects/relationship.test.ts index ed895fb3..731f379a 100644 --- a/test/objects/relationship.test.ts +++ b/test/objects/relationship.test.ts @@ -3,21 +3,21 @@ import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import { z } from 'zod'; import { createSyntheticStixObject } from '../../src/generator'; import { - type Description, - type ExternalReferences, - type StixCreatedTimestamp, - type StixIdentifier, - type StixModifiedTimestamp, - type StixSpecVersion, - type StixType + type Description, + type ExternalReferences, + type StixCreatedTimestamp, + type StixIdentifier, + type StixModifiedTimestamp, + type StixSpecVersion, + type StixType, } from '../../src/schemas/common/index'; import { - invalidRelationships, - isValidRelationship, - type Relationship, - relationshipSchema, - type RelationshipType, - validRelationshipObjectTypes + invalidRelationships, + isValidRelationship, + type Relationship, + relationshipSchema, + type RelationshipType, + validRelationshipObjectTypes, } from '../../src/schemas/sro/relationship.schema'; import { logger } from '../utils/logger'; @@ -283,7 +283,7 @@ describe('RelationshipSchema', () => { const validRelationships: Relationship[] = []; const errors: { relationship: Relationship; issues: z.ZodIssue[] }[] = []; - for (let relationship of relationships) { + for (const relationship of relationships) { const result = relationshipSchema.safeParse(relationship); if (result.success) { validRelationships.push(relationship); diff --git a/test/objects/stix-bundle.test.ts b/test/objects/stix-bundle.test.ts index ee0435d4..bca84032 100644 --- a/test/objects/stix-bundle.test.ts +++ b/test/objects/stix-bundle.test.ts @@ -42,7 +42,6 @@ describe('StixBundleSchema', () => { minimalBundle = { id: `bundle--${uuidv4()}`, type: 'bundle', - spec_version: '2.1', objects: [minimalCollection], }; }); @@ -119,10 +118,6 @@ describe('StixBundleSchema', () => { testField('type', 'invalid-type'); }); - describe('spec_version', () => { - testField('spec_version', 'invalid-version'); - }); - describe('objects', () => { it('should reject invalid objects array (non-array value)', () => { const invalidObjectsArray = { diff --git a/test/objects/technique.test.ts b/test/objects/technique.test.ts index e70c51a1..fe5a0610 100644 --- a/test/objects/technique.test.ts +++ b/test/objects/technique.test.ts @@ -1,8 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { createSyntheticStixObject } from '../../src/generator'; -import { - xMitreIdentity -} from '../../src/schemas/common/index'; +import { xMitreIdentity } from '../../src/schemas/common/index'; import { type Technique, techniqueSchema } from '../../src/schemas/sdo/technique.schema'; /** diff --git a/tsconfig.json b/tsconfig.json index c947c1e9..d4387ea4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,4 @@ -{ +{ // These settings were informed by the TypeScript documentation for writing TypeScript libraries: // https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-a-library "compilerOptions": { @@ -22,10 +22,9 @@ "rootDir": "src", // Configures module resolution paths, allowing "@/" to resolve to "./src/" "paths": { - "@/*": [ - "./src/*" - ] - }, + "@/*": ["./src/*"], + "@mitre-attack/attack-data-model": ["./src/index.ts"] + } }, // Specifies which files to include in the compilation "include": [ diff --git a/vitest.config.ts b/vitest.config.ts index 162abed2..3849330a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,15 +2,15 @@ import { defineConfig } from 'vitest/config'; import path from 'path'; export default defineConfig({ - test: { - environment: 'node', - globals: true, - include: ['test/**/*.test.ts'], - setupFiles: ['./test/vitest.setup.ts'], + test: { + environment: 'node', + globals: true, + include: ['test/**/*.test.ts'], + setupFiles: ['./test/vitest.setup.ts'], + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src') - } - }, -}); \ No newline at end of file + }, +});