UNPKG

@mitre-attack/attack-data-model

Version:

A TypeScript API for the MITRE ATT&CK data model

467 lines (452 loc) 18.8 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/schemas/common/property-schemas/stix-extensions.ts var stix_extensions_exports = {}; __export(stix_extensions_exports, { extensionDefinitionSchema: () => extensionDefinitionSchema, extensionObjectTypeSchema: () => extensionObjectTypeSchema, extensionSchema: () => extensionSchema, extensionTypeSchema: () => extensionTypeSchema, extensionsSchema: () => extensionsSchema }); module.exports = __toCommonJS(stix_extensions_exports); var import_v47 = require("zod/v4"); // src/schemas/common/property-schemas/generics.ts var import_zod = require("zod"); var nonEmptyRequiredString = import_zod.z.string().trim().min(1, { error: "At least one character is required. Whitespace is not permissible." }); var emptyStixListErrorMessage = "Empty lists are prohibited in STIX and MUST NOT be used as a substitute for omitting the property if it is optional. The list MUST be present and MUST have at least one value."; var stixListOfString = import_zod.z.array(nonEmptyRequiredString).min(1, { error: emptyStixListErrorMessage }); // src/schemas/common/property-schemas/stix-attribution.ts var import_v43 = require("zod/v4"); // src/schemas/common/property-schemas/stix-id.ts var import_v42 = require("zod/v4"); // src/schemas/common/property-schemas/stix-type.ts var import_v4 = require("zod/v4"); var stixTypeToTypeName = { "attack-pattern": "Technique", bundle: "StixBundle", campaign: "Campaign", "course-of-action": "Mitigation", "extension-definition": null, identity: "Identity", "intrusion-set": "Group", malware: "Malware", tool: "Tool", "marking-definition": "MarkingDefinition", "x-mitre-analytic": "Analytic", "x-mitre-data-component": "DataComponent", "x-mitre-detection-strategy": "DetectionStrategy", "x-mitre-data-source": "DataSource", "x-mitre-tactic": "Tactic", "x-mitre-asset": "Asset", "x-mitre-matrix": "Matrix", "x-mitre-collection": "Collection", relationship: "Relationship", file: "", // not used in ATT&CK but used in sample_refs for Malware artifact: "" // not used in ATT&CK but used in sample_refs for Malware // 'observed-data': 'ObservedData', // not used in ATT&CK // 'report': 'Report', // not used in ATT&CK // 'threat-actor': 'ThreatActor', // not used in ATT&CK // 'vulnerability': 'Vulnerability', // not used in ATT&CK }; var supportedStixTypes = [ "attack-pattern", "bundle", "campaign", "course-of-action", "extension-definition", "identity", "intrusion-set", "malware", "tool", "marking-definition", "x-mitre-analytic", "x-mitre-data-component", "x-mitre-detection-strategy", "x-mitre-tactic", "x-mitre-asset", "x-mitre-data-source", "x-mitre-matrix", "x-mitre-collection", "relationship", "file", // not used in ATT&CK but used in sample_refs for Malware "artifact" // not used in ATT&CK but used in sample_refs for Malware // "indicator", // not used in ATT&CK // "observed-data", // not used in ATT&CK // "report", // not used in ATT&CK // "threat-actor", // not used in ATT&CK // "vulnerability", // not used in ATT&CK ]; var stixTypeSchema = import_v4.z.enum(supportedStixTypes, { error: (issue) => { if (issue.code === "invalid_value") { const received = typeof issue.input === "string" ? issue.input : String(issue.input); return `Invalid STIX type '${received}'. Expected one of the supported STIX types.`; } return void 0; } }).meta({ description: "The type property identifies the type of STIX Object (SDO, Relationship Object, etc). The value of the type field MUST be one of the types defined by a STIX Object (e.g., indicator)." }); function createStixTypeValidator(stixType) { const objectName = stixTypeToTypeName[stixType]; return import_v4.z.literal(stixType).refine((val) => val === stixType, { error: (issue) => `Invalid 'type' property. Expected '${stixType}' for ${objectName} object, but received '${issue.input}'.` }); } // src/schemas/common/property-schemas/stix-id.ts var stixIdentifierSchema = import_v42.z.string().refine((val) => val.includes("--") && val.split("--").length === 2, { error: (issue) => ({ code: "custom", message: "Invalid STIX Identifier: must comply with format 'type--UUIDv4'", input: issue.input, path: [] }) }).refine( (val) => { const [type] = val.split("--"); return stixTypeSchema.safeParse(type).success; }, { error: (issue) => { const val = issue.input; const [type] = val.split("--"); const typeName = type in stixTypeToTypeName ? stixTypeToTypeName[type] : "STIX"; return { code: "custom", message: `Invalid STIX Identifier for ${typeName} object: contains invalid STIX type '${type}'`, input: issue.input, path: [] }; } } ).refine( (val) => { const [, uuid] = val.split("--"); return import_v42.z.uuid().safeParse(uuid).success; }, { error: (issue) => { const val = issue.input; const [type] = val.split("--"); const typeName = type in stixTypeToTypeName ? stixTypeToTypeName[type] : "STIX"; return { code: "custom", message: `Invalid STIX Identifier for ${typeName} object: contains invalid UUIDv4 format`, input: issue.input, path: [] }; } } ).meta({ description: "Represents identifiers across the CTI specifications. The format consists of the name of the top-level object being identified, followed by two dashes (--), followed by a UUIDv4." }); function createStixIdValidator(expectedType) { return stixIdentifierSchema.refine( (val) => val.startsWith(`${expectedType}--`), { error: () => ({ code: "custom", message: `Invalid STIX Identifier: must start with '${expectedType}--'`, input: expectedType, path: [] }) } ); } // src/schemas/common/property-schemas/stix-attribution.ts var objectMarkingRefsSchema = import_v43.z.array( stixIdentifierSchema.startsWith( "marking-definition--", 'Identifier must start with "marking-definition--"' ) ).meta({ description: "The list of marking-definition objects to be applied to this object." }); var stixCreatedByRefSchema = createStixIdValidator("identity").meta({ description: "The created_by_ref property specifies the id property of the identity object that describes the entity that created this object. If this attribute is omitted, the source of this information is undefined. This may be used by object creators who wish to remain anonymous." }); // src/schemas/common/property-schemas/stix-common-properties.ts var import_v44 = require("zod/v4"); var descriptionSchema = nonEmptyRequiredString.meta({ description: "A description of the object." }); var nameSchema = nonEmptyRequiredString.meta({ description: "The name of the object." }); var aliasesSchema = stixListOfString.meta({ description: "Alternative names used to identify this object. The first alias must match the object's name." }); // src/schemas/common/property-schemas/stix-external-references.ts var import_zod2 = __toESM(require("zod"), 1); // src/schemas/common/property-schemas/attack-id.ts var import_v45 = require("zod/v4"); var attackIdConfig = { tactic: { pattern: /^TA\d{4}$/, message: "Must match ATT&CK Tactic ID format (TA####)", example: "TA####", stixTypes: ["x-mitre-tactic"] }, technique: { pattern: /^T\d{4}$/, message: "Must match ATT&CK Technique ID format (T####)", example: "T####", stixTypes: ["attack-pattern"] // Note: attack-pattern can be technique or subtechnique }, subtechnique: { pattern: /^T\d{4}\.\d{3}$/, message: "Must match ATT&CK Sub-technique ID format (T####.###)", example: "T####.###", stixTypes: ["attack-pattern"] // Note: attack-pattern can be technique or subtechnique }, group: { pattern: /^G\d{4}$/, message: "Must match ATT&CK Group ID format (G####)", example: "G####", stixTypes: ["intrusion-set"] }, software: { pattern: /^S\d{4}$/, message: "Must match ATT&CK Software ID format (S####)", example: "S####", stixTypes: ["malware", "tool"] }, mitigation: { pattern: /^M\d{4}$/, message: "Must match ATT&CK Mitigation ID format (M####)", example: "M####", stixTypes: ["course-of-action"] }, asset: { pattern: /^A\d{4}$/, message: "Must match ATT&CK Asset ID format (A####)", example: "A####", stixTypes: ["x-mitre-asset"] }, "data-source": { pattern: /^DS\d{4}$/, message: "Must match ATT&CK Data Source ID format (DS####)", example: "DS####", stixTypes: ["x-mitre-data-source"] }, campaign: { pattern: /^C\d{4}$/, message: "Must match ATT&CK Campaign ID format (C####)", example: "C####", stixTypes: ["campaign"] }, "data-component": { pattern: /^DC\d{4}$/, message: "Must match ATT&CK Data Component Source ID format (DC####)", example: "DC####", stixTypes: ["x-mitre-data-component"] }, "detection-strategy": { pattern: /^DET\d{4}$/, message: "Must match ATT&CK Detection Strategy Source ID format (DET####)", example: "DET####", stixTypes: ["x-mitre-detection-strategy"] }, analytic: { pattern: /^AN\d{4}$/, message: "Must match ATT&CK Analytic Source ID format (AN####)", example: "AN####", stixTypes: ["x-mitre-analytic"] } }; var attackIdPatterns = Object.fromEntries( Object.entries(attackIdConfig).map(([key, config]) => [key, config.pattern]) ); var attackIdMessages = Object.fromEntries( Object.entries(attackIdConfig).map(([key, config]) => [key, config.message]) ); var attackIdExamples = Object.fromEntries( Object.entries(attackIdConfig).map(([key, config]) => [key, config.example]) ); var oldAttackIdRegex = /^MOB-(M|S)\d{4}$/; var xMitreOldAttackIdSchema = nonEmptyRequiredString.refine( (value) => { return oldAttackIdRegex.test(value); }, { message: "Must be in the format 'MOB-X0000' where X is either 'M' or 'S', followed by exactly four digits" } ).meta({ description: "Old ATT&CK IDs that may have been associated with this object" }); // src/schemas/common/property-schemas/stix-external-references.ts var externalReferenceSchema = import_zod2.default.object({ source_name: nonEmptyRequiredString, description: nonEmptyRequiredString.optional(), url: import_zod2.default.url({ error: (issue) => issue.input === null ? "URL cannot be null" : "Invalid URL format. Please provide a valid URL" }).optional(), external_id: nonEmptyRequiredString.optional() }); var externalReferencesSchema = import_zod2.default.array(externalReferenceSchema).min(1).meta({ description: "A list of external references which refers to non-STIX information" }); // src/schemas/common/property-schemas/stix-granular-marking.ts var import_zod3 = require("zod"); var granularMarkingSchema = import_zod3.z.object({ lang: nonEmptyRequiredString.optional().meta({ description: "The lang property identifies the language of the text identified by this marking. The value of the lang property, if present, MUST be an [RFC5646] language code. If the marking_ref property is not present, this property MUST be present. If the marking_ref property is present, this property MUST NOT be present." }), marking_ref: stixIdentifierSchema.optional().meta({ description: "The marking_ref property specifies the ID of the marking-definition object that describes the marking. If the lang property is not present, this property MUST be present. If the lang property is present, this property MUST NOT be present." }), selectors: stixListOfString.meta({ description: "The selectors property specifies a list of selectors for content contained within the STIX Object in which this property appears." }) }).check((ctx) => { const { lang, marking_ref } = ctx.value; const hasLang = lang !== void 0; const hasMarkingRef = marking_ref !== void 0; if (hasLang && hasMarkingRef) { ctx.issues.push({ path: ["lang"], message: "If the marking_ref property is present, the lang property MUST NOT be present.", code: "custom", input: { lang: ctx.value.lang, marking_ref: ctx.value.marking_ref } }); ctx.issues.push({ path: ["marking_ref"], message: "If the lang property is present, the marking_ref property MUST NOT be present.", code: "custom", input: { lang: ctx.value.lang, marking_ref: ctx.value.marking_ref } }); } else if (!hasLang && !hasMarkingRef) { ctx.issues.push({ path: ["lang"], message: "If the marking_ref property is not present, the lang property MUST be present.", code: "custom", input: { lang: ctx.value.lang, marking_ref: ctx.value.marking_ref } }); ctx.issues.push({ path: ["marking_ref"], message: "If the lang property is not present, the marking_ref property MUST be present.", code: "custom", input: { lang: ctx.value.lang, marking_ref: ctx.value.marking_ref } }); } }).meta({ description: "The `granular-marking` type defines how the `marking-definition` object referenced by the **marking_ref** property or a language specified by the **lang** property applies to a set of content identified by the list of selectors in the selectors property." }); // src/schemas/common/property-schemas/stix-versioning.ts var import_v46 = require("zod/v4"); var specVersionDescription = [ "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." ].join(" "); var stixSpecVersionSchema = import_v46.z.literal("2.1").meta({ description: specVersionDescription }); // src/schemas/common/property-schemas/stix-extensions.ts var extensionTypeSchema = import_v47.z.enum([ "new-sdo", "new-sco", "new-sro", "property-extension", "toplevel-property-extension" ]); var extensionSchema = import_v47.z.object({ extension_type: extensionTypeSchema // Additional properties depend on extension type - using record for flexibility }).catchall(import_v47.z.unknown()); var extensionsSchema = import_v47.z.record( nonEmptyRequiredString, import_v47.z.union([extensionSchema, import_v47.z.record(nonEmptyRequiredString, import_v47.z.unknown())]) ).meta({ description: "Specifies any extensions of the object, as a dictionary where keys are extension definition UUIDs" }); var extensionPropertyNameSchema = import_v47.z.string().trim().min(3, "Extension property names must be at least 3 characters").max(250, "Extension property names must be no longer than 250 characters").regex( /^[a-z0-9_]+$/, "Extension property names must only contain lowercase letters, digits, and underscores" ); var extensionObjectTypeSchema = import_v47.z.string().trim().min(3, "Extension object type must be at least 3 characters").max(250, "Extension object type must be no longer than 250 characters").regex( /^[a-z0-9-]+$/, "Extension object type must only contain lowercase letters, digits, and hyphens" ).refine( (value) => !value.includes("--"), "Extension object type must not contain consecutive hyphens" ); var extensionDefinitionSchema = import_v47.z.object({ // Required common properties id: createStixIdValidator("extension-definition"), type: createStixTypeValidator("extension-definition"), spec_version: stixSpecVersionSchema, created: import_v47.z.iso.datetime(), modified: import_v47.z.iso.datetime(), created_by_ref: stixCreatedByRefSchema, // Optional common properties revoked: import_v47.z.boolean().optional(), labels: stixListOfString.optional(), external_references: externalReferencesSchema.optional(), object_marking_refs: objectMarkingRefsSchema.optional(), granular_markings: import_v47.z.array(granularMarkingSchema).optional(), // Extension definition specific properties name: nameSchema, description: nonEmptyRequiredString.optional(), schema: nonEmptyRequiredString, version: nonEmptyRequiredString.regex( /^\d+\.\d+\.\d+$/, "Version must follow semantic versioning (MAJOR.MINOR.PATCH)" ), extension_types: import_v47.z.array(extensionTypeSchema).min(1, "At least one extension type is required"), extension_properties: import_v47.z.array(extensionPropertyNameSchema).optional() }).strict().refine( (data) => { if (data.extension_types.includes("toplevel-property-extension")) { return data.extension_properties && data.extension_properties.length > 0; } return true; }, { message: "extension_properties must be provided when extension_types includes toplevel-property-extension" } ).meta({ description: "Extension Definition object allows producers to extend existing STIX objects or create new STIX objects" }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { extensionDefinitionSchema, extensionObjectTypeSchema, extensionSchema, extensionTypeSchema, extensionsSchema });