UNPKG

@mitre-attack/attack-data-model

Version:

A TypeScript API for the MITRE ATT&CK data model

1,042 lines (1,030 loc) 38.6 kB
import { markingDefinitionSchema } from "./chunk-NGT3NXS6.js"; import { matrixSchema } from "./chunk-I3N3UE6S.js"; import { mitigationSchema } from "./chunk-XQK5DHA7.js"; import { softwareSchema } from "./chunk-77N4A5RV.js"; import { tacticSchema } from "./chunk-OBBW2YZ2.js"; import { collectionSchema } from "./chunk-SWPQKEG3.js"; import { dataComponentSchema } from "./chunk-LUZFW5GQ.js"; import { dataSourceSchema } from "./chunk-WKPLIQD4.js"; import { detectionStrategySchema } from "./chunk-QCHCLAHE.js"; import { identitySchema } from "./chunk-IATBMSJK.js"; import { logSourceSchema } from "./chunk-OG5WXWVB.js"; import { analyticSchema } from "./chunk-BCTQGLCT.js"; import { assetSchema } from "./chunk-H47PCQWS.js"; import { attackBaseDomainObjectSchema, attackBaseRelationshipObjectSchema } from "./chunk-ZQ5CIHH7.js"; import { stixTimestampSchema } from "./chunk-O4MZPUSY.js"; import { stixSpecVersionSchema } from "./chunk-36L755UT.js"; import { aliasesSchema, attackDomainSchema, createOldMitreAttackIdSchema, descriptionSchema, killChainPhaseSchema, xMitreContributorsSchema, xMitreDomainsSchema, xMitreModifiedByRefSchema, xMitrePlatformsSchema } from "./chunk-Z7F5EWOT.js"; import { createAttackExternalReferencesSchema } from "./chunk-QY7EQ3UO.js"; import { attackIdExamples, attackIdPatterns } from "./chunk-RWOQWV2O.js"; import { createStixIdValidator, stixIdentifierSchema } from "./chunk-OM2DJ5DL.js"; import { createStixTypeValidator, stixTypeSchema } from "./chunk-5JU73PGM.js"; import { AttackMotivationOV, AttackResourceLevelOV, ImplementationLanguageOV, MalwareCapabilityOV, MalwareTypeOV, ProcessorArchitectureOV, ToolTypeOV } from "./chunk-YIPWHVL6.js"; // src/refinements/index.ts import "zod/v4"; // src/schemas/sdo/campaign.schema.ts import { z } from "zod/v4"; var baseCitationSchema = z.custom( (value) => { if (typeof value !== "string") return false; if (!value.startsWith("(") || !value.endsWith(")")) return false; const content = value.slice(1, -1); const parts = content.split(":"); if (parts.length !== 2) return false; if (parts[0].trim() !== "Citation") return false; if (parts[1].trim() === "") return false; return true; }, { message: "Each citation must conform to the pattern '(Citation: [citation name])'" } ); var multipleCitationsSchema = z.custom( (value) => { if (typeof value !== "string") return false; const citations = value.match(/\(Citation:[^)]+\)/g); if (!citations) return false; return citations.join("") === value && citations.every((citation) => baseCitationSchema.safeParse(citation).success); }, { message: "Must be one or more citations in the form '(Citation: [citation name])' without any separators" } ); var xMitreFirstSeenCitationSchema = multipleCitationsSchema.meta({ description: "One or more citations for when the object was first seen, in the form '(Citation: [citation name])(Citation: [citation name])...', where each [citation name] can be found as one of the source_name values in the external_references." }); var xMitreLastSeenCitationSchema = multipleCitationsSchema.meta({ description: "One or more citations for when the object was last seen, in the form '(Citation: [citation name])(Citation: [citation name])...', where each [citation name] can be found as one of the source_name values in the external_references." }); var extensibleCampaignSchema = attackBaseDomainObjectSchema.extend({ id: createStixIdValidator("campaign"), type: createStixTypeValidator("campaign"), description: descriptionSchema, external_references: createAttackExternalReferencesSchema("campaign"), x_mitre_domains: xMitreDomainsSchema, x_mitre_modified_by_ref: xMitreModifiedByRefSchema, x_mitre_contributors: xMitreContributorsSchema.optional(), aliases: aliasesSchema, // Optional in STIX but required in ATT&CK first_seen: stixTimestampSchema.meta({ description: "The time that this Campaign was first seen." }), // Optional in STIX but required in ATT&CK last_seen: stixTimestampSchema.meta({ description: "The time that this Campaign was last seen." }), x_mitre_first_seen_citation: xMitreFirstSeenCitationSchema, x_mitre_last_seen_citation: xMitreLastSeenCitationSchema }).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 revoked: true // Optional in STIX but required in ATT&CK }).strict(); var campaignSchema = extensibleCampaignSchema.check((ctx) => { createFirstAliasRefinement()(ctx); createCitationsRefinement()(ctx); }); // src/schemas/sdo/group.schema.ts import { z as z2 } from "zod/v4"; var extensibleGroupSchema = attackBaseDomainObjectSchema.extend({ id: createStixIdValidator("intrusion-set"), type: createStixTypeValidator("intrusion-set"), // Not used in ATT&CK Group but defined in STIX description: z2.string().optional().meta({ description: "A description that provides more details and context about the Intrusion Set, potentially including its purpose and its key characteristics" }), // Optional in STIX but required in ATT&CK external_references: createAttackExternalReferencesSchema("intrusion-set"), x_mitre_domains: xMitreDomainsSchema, x_mitre_contributors: z2.array(z2.string()).optional(), x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional(), aliases: aliasesSchema.optional().meta({ description: "Alternative names used to identify this group. The first alias must match the object's name" }), // Not used in ATT&CK Group but defined in STIX first_seen: stixTimestampSchema.optional().meta({ description: "The time that this Intrusion Set was first seen" }), // Not used in ATT&CK Group but defined in STIX last_seen: stixTimestampSchema.optional().meta({ description: "The time that this Intrusion Set was last seen" }), // Not used in ATT&CK Group but defined in STIX goals: z2.array(z2.string()).optional().meta({ description: "The high-level goals of this Intrusion Set, namely, what are they trying to do" }), // Not used in ATT&CK Group but defined in STIX resource_level: AttackResourceLevelOV.optional().meta({ description: "This property specifies the organizational level at which this Intrusion Set typically works, which in turn determines the resources available to this Intrusion Set for use in an attack" }), primary_motivation: AttackMotivationOV.optional().meta({ description: "The primary reason, motivation, or purpose behind this Intrusion Set" }), secondary_motivations: z2.array(AttackMotivationOV).optional().meta({ description: "The secondary reasons, motivations, or purposes behind this Intrusion Set" }) }).strict(); var groupSchema = extensibleGroupSchema.check((ctx) => { createFirstAliasRefinement()(ctx); }); // src/schemas/sdo/malware.schema.ts import { z as z3 } from "zod/v4"; var stixFileType = createStixIdValidator("file").meta({ description: "Used to specify the file stixType of the sample_refs property." }); var stixArtifactType = createStixIdValidator("artifact").meta({ description: "Used to specify the artifact stixType of the sample_refs property." }); var extensibleMalwareSchema = softwareSchema.extend({ id: createStixIdValidator("malware"), type: createStixTypeValidator("malware"), is_family: z3.boolean().meta({ description: "Whether the object represents a malware family (if true) or a malware instance (if false)" }), // Not used in ATT&CK Malware but defined in STIX malware_types: z3.array(MalwareTypeOV).meta({ description: "A set of categorizations for the malware being described." }).optional(), // Not used in ATT&CK Malware but defined in STIX kill_chain_phases: z3.array(killChainPhaseSchema).meta({ description: "The list of Kill Chain Phases for which this malware can be used." }).optional(), // Not used in ATT&CK Malware but defined in STIX first_seen: stixTimestampSchema.optional().meta({ description: "The time that this malware instance or malware family was first seen." }), // Not used in ATT&CK Malware but defined in STIX last_seen: stixTimestampSchema.optional().meta({ description: "The time that this malware family or malware instance was last seen." }), external_references: createAttackExternalReferencesSchema("malware"), x_mitre_old_attack_id: createOldMitreAttackIdSchema("malware").optional(), // Not used in ATT&CK Malware but defined in STIX os_execution_envs: z3.array(z3.string()).meta({ description: "The operating systems that the malware family or malware instance is executable on. This applies to virtualized operating systems as well as those running on bare metal." }).optional(), // Not used in ATT&CK Malware but defined in STIX architecture_execution_envs: z3.array(ProcessorArchitectureOV).meta({ description: "The processor architectures (e.g., x86, ARM, etc.) that the malware instance or family is executable on." }).optional(), // Not used in ATT&CK Malware but defined in STIX implementation_languages: z3.array(ImplementationLanguageOV).meta({ description: "The programming language(s) used to implement the malware instance or family." }).optional(), // Not used in ATT&CK Malware but defined in STIX capabilities: z3.array(MalwareCapabilityOV).meta({ description: "Any of the capabilities identified for the malware instance or family." }).optional(), // Not used in ATT&CK Malware but defined in STIX sample_refs: z3.array(z3.union([stixArtifactType, stixFileType])).optional().meta({ description: "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(); var validateFirstXMitreAlias = createFirstXMitreAliasRefinement(); var validateFirstAlias = createFirstAliasRefinement(); var malwareSchema = extensibleMalwareSchema.check((ctx) => { validateFirstAlias(ctx); validateFirstXMitreAlias(ctx); }); // src/schemas/sdo/technique.schema.ts import { z as z4 } from "zod/v4"; var xMitreNetworkRequirementsSchema = z4.boolean().meta({ description: "Requires network to execute the technique" }); var supportedMitreEffectivePermissions = ["Administrator", "SYSTEM", "User", "root"]; var xMitreEffectivePermissionsSchema = z4.array( z4.enum(supportedMitreEffectivePermissions, { error: () => `Effective permission must be one of: ${supportedMitreEffectivePermissions.join(", ")}` }), { error: (issue) => issue.code === "invalid_type" ? "x_mitre_effective_permissions must be an array of strings" : "Invalid effective permissions array" } ).nonempty("At least one effective permission is required").refine((items) => new Set(items).size === items.length, { message: "Effective permissions must be unique (no duplicates allowed)" }).meta({ description: "The level of permissions the adversary will attain by performing the technique" }); var supportedMitreImpactTypes = ["Availability", "Integrity"]; var xMitreImpactTypeSchema = z4.array( z4.enum(supportedMitreImpactTypes, { error: () => `Impact type must be one of: ${supportedMitreImpactTypes.join(", ")}` }), { error: (issue) => issue.code === "invalid_type" ? "x_mitre_impact_type must be an array of strings" : "Invalid impact type array" } ).meta({ description: "Denotes if the technique can be used for integrity or availability attacks" }); var xMitreSystemRequirementsSchema = z4.array(z4.string(), { error: (issue) => issue.code === "invalid_type" ? "x_mitre_system_requirements must be an array of strings" : "Invalid system requirements array" }).meta({ description: "Additional information on requirements the adversary needs to meet or about the state of the system (software, patch level, etc.) that may be required for the technique to work" }); var xMitreRemoteSupportSchema = z4.boolean().meta({ description: "If true, the technique can be used to execute something on a remote system." }); var supportedMitrePermissionsRequired = [ "Remote Desktop Users", "SYSTEM", "Administrator", "root", "User" ]; var xMitrePermissionsRequiredSchema = z4.array( z4.enum(supportedMitrePermissionsRequired, { error: () => "x_mitre_permissions_required may only contain values from the following list: " + supportedMitrePermissionsRequired.join(", ") }), { error: (issue) => issue.code === "invalid_type" ? "x_mitre_permissions_required must be an array of strings" : "Invalid x_mitre_permissions_required array" } ).nonempty("At least one x_mitre_permissions_required value is required").meta({ description: "The lowest level of permissions the adversary is required to be operating within to perform the technique on a system." }); var xMitreDataSourceSchema = z4.custom( (value) => { if (typeof value !== "string") return false; const parts = value.split(":"); return parts.length === 2 && parts[0].trim() !== "" && parts[1].trim() !== ""; }, { message: "Each entry must conform to the pattern '<Data Source Name>: <Data Component Name>'" } ).meta({ description: "A single data source in the format 'Data Source Name: Data Component Name'" }); var xMitreDataSourcesSchema = z4.array(xMitreDataSourceSchema, { error: (issue) => issue.code === "invalid_type" ? "x_mitre_data_sources must be an array of strings" : "Invalid data sources array" }).nonempty().meta({ description: "Sources of information that may be used to identify the action or result of the action being performed" }); var xMitreIsSubtechniqueSchema = z4.boolean({ error: "x_mitre_is_subtechnique must be a boolean" }).meta({ description: "If true, this attack-pattern is a sub-technique" }); var supportedMitreTacticTypes = [ "Post-Adversary Device Access", "Pre-Adversary Device Access", // TODO only used with PRE-ATT&CK "Without Adversary Device Access" ]; var xMitreTacticTypeSchema = z4.array( z4.enum(supportedMitreTacticTypes, { error: () => `Tactic type must be one of: ${supportedMitreTacticTypes.join(", ")}` }), { error: (issue) => issue.code === "invalid_type" ? "x_mitre_tactic_type must be an array of strings" : "Invalid tactic type array" } ).meta({ description: '"Post-Adversary Device Access", "Pre-Adversary Device Access", or "Without Adversary Device Access"' }); var supportedMitreDefenseBypasses = [ "Signature-based detection", "Multi-Factor Authentication", "Network Intrusion Detection System", "Application Control", "Host forensic analysis", "Exploit Prevention", "Signature-based Detection", "Data Execution Prevention", "Heuristic Detection", "File system access controls", "File Monitoring", "Digital Certificate Validation", "Logon Credentials", "Firewall", "Host Forensic Analysis", "Static File Analysis", "Heuristic detection", "Notarization", "System access controls", "Binary Analysis", "Web Content Filters", "Network intrusion detection system", "Host intrusion prevention systems", "Application control", "Defensive network service scanning", "User Mode Signature Validation", "Encryption", "Log Analysis", "Autoruns Analysis", "Anti Virus", "Gatekeeper", "Anti-virus", "Log analysis", "Process whitelisting", "Host Intrusion Prevention Systems", "Windows User Account Control", "System Access Controls", "Application whitelisting", "Whitelisting by file name or path", "File monitoring" ]; var xMitreDefenseBypassesSchema = z4.array(z4.enum(supportedMitreDefenseBypasses)).min(1).refine((items) => new Set(items).size === items.length, { message: "Mitre defense bypasses must be unique (no duplicates allowed)." }).meta({ description: "List of defensive tools, methodologies, or processes the technique can bypass." }); var xMitreDetectionSchema = z4.string({ error: "x_mitre_detection must be a string." }).meta({ description: "Strategies for identifying if a technique has been used by an adversary." }); var extensibleTechniqueSchema = attackBaseDomainObjectSchema.extend({ id: createStixIdValidator("attack-pattern"), type: createStixTypeValidator("attack-pattern"), // Optional in STIX but required in ATT&CK external_references: createAttackExternalReferencesSchema("attack-pattern"), kill_chain_phases: z4.array(killChainPhaseSchema).optional(), description: descriptionSchema.optional(), x_mitre_platforms: xMitrePlatformsSchema.optional(), x_mitre_detection: xMitreDetectionSchema.optional(), x_mitre_is_subtechnique: xMitreIsSubtechniqueSchema, x_mitre_data_sources: xMitreDataSourcesSchema.optional(), // TODO remove in attack spec 4.0.0 / adm release 5.x x_mitre_defense_bypassed: xMitreDefenseBypassesSchema.optional(), x_mitre_contributors: xMitreContributorsSchema.optional(), x_mitre_permissions_required: xMitrePermissionsRequiredSchema.optional(), x_mitre_remote_support: xMitreRemoteSupportSchema.optional(), x_mitre_system_requirements: xMitreSystemRequirementsSchema.optional(), x_mitre_impact_type: xMitreImpactTypeSchema.optional(), x_mitre_effective_permissions: xMitreEffectivePermissionsSchema.optional(), x_mitre_network_requirements: xMitreNetworkRequirementsSchema.optional(), x_mitre_tactic_type: xMitreTacticTypeSchema.optional(), x_mitre_domains: xMitreDomainsSchema, x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional() }).strict(); var techniqueSchema = extensibleTechniqueSchema.check((ctx) => { createAttackIdInExternalReferencesRefinement()(ctx); createEnterpriseOnlyPropertiesRefinement()(ctx); createMobileOnlyPropertiesRefinement()(ctx); }); // src/schemas/sdo/tool.schema.ts import { z as z5 } from "zod/v4"; var extensibleToolSchema = softwareSchema.extend({ id: createStixIdValidator("tool"), type: createStixTypeValidator("tool"), external_references: createAttackExternalReferencesSchema("tool"), // Not used in ATT&CK Tool but defined in STIX tool_types: z5.array(ToolTypeOV).optional().meta({ description: "The kind(s) of tool(s) being described." }), // Not used in ATT&CK Tool but defined in STIX kill_chain_phases: z5.array(killChainPhaseSchema).optional().meta({ description: "The list of kill chain phases for which this Tool can be used." }), // Not used in ATT&CK Tool but defined in STIX tool_version: z5.string().optional().meta({ description: "The version identifier associated with the Tool" }), x_mitre_old_attack_id: createOldMitreAttackIdSchema("tool").optional() }).strict(); var toolSchema = extensibleToolSchema.check((ctx) => { createFirstXMitreAliasRefinement()(ctx); createFirstAliasRefinement()(ctx); }); // src/schemas/sdo/stix-bundle.schema.ts import { z as z7 } from "zod/v4"; // src/schemas/sro/relationship.schema.ts import { z as z6 } from "zod/v4"; var supportedRelationshipTypes = [ "uses", "mitigates", "subtechnique-of", "detects", "attributed-to", "targets", "revoked-by", "found-in" ]; var relationshipTypeSchema = z6.enum(supportedRelationshipTypes).meta({ description: "The name used to identify the type of Relationship." }); var validRelationshipObjectTypes = [ stixTypeSchema.enum["attack-pattern"], stixTypeSchema.enum["campaign"], stixTypeSchema.enum["course-of-action"], stixTypeSchema.enum["intrusion-set"], stixTypeSchema.enum["malware"], stixTypeSchema.enum["tool"], stixTypeSchema.enum["x-mitre-data-component"], stixTypeSchema.enum["x-mitre-asset"], stixTypeSchema.enum["x-mitre-log-source"] ]; var relationshipMap = { uses: { source: [ stixTypeSchema.enum.malware, stixTypeSchema.enum.tool, stixTypeSchema.enum["intrusion-set"], stixTypeSchema.enum.campaign ], target: [ stixTypeSchema.enum["attack-pattern"], stixTypeSchema.enum.malware, stixTypeSchema.enum.tool ] }, mitigates: { source: [stixTypeSchema.enum["course-of-action"]], target: [stixTypeSchema.enum["attack-pattern"]] }, "subtechnique-of": { source: [stixTypeSchema.enum["attack-pattern"]], target: [stixTypeSchema.enum["attack-pattern"]] }, detects: { source: [ stixTypeSchema.enum["x-mitre-data-component"], // TODO remove in attack spec 4.0.0 / adm release 5.x stixTypeSchema.enum["x-mitre-detection-strategy"] ], target: [stixTypeSchema.enum["attack-pattern"]] }, "attributed-to": { source: [stixTypeSchema.enum.campaign], target: [stixTypeSchema.enum["intrusion-set"]] }, targets: { source: [stixTypeSchema.enum["attack-pattern"]], target: [stixTypeSchema.enum["x-mitre-asset"]] }, "revoked-by": { source: validRelationshipObjectTypes, target: validRelationshipObjectTypes }, "found-in": { source: [stixTypeSchema.enum["x-mitre-data-component"]], target: [stixTypeSchema.enum["x-mitre-log-source"]] } }; var invalidUsesRelationships = [ [stixTypeSchema.enum.malware, stixTypeSchema.enum.malware], [stixTypeSchema.enum.malware, stixTypeSchema.enum.tool], [stixTypeSchema.enum.tool, stixTypeSchema.enum.malware], [stixTypeSchema.enum.tool, stixTypeSchema.enum.tool] ]; function isValidRelationship(sourceType, relationshipType, targetType, errorCollector) { const mapping = relationshipMap[relationshipType]; if (!mapping) { if (errorCollector) { errorCollector({ message: `Invalid 'relationship_type': ${relationshipType}. Must be one of ${Object.keys(relationshipMap).join(", ")}.`, code: "custom", path: ["relationship_type"], input: { relationship_type: relationshipType, source_type: sourceType, target_type: targetType } }); } return false; } if (!mapping.source.includes(sourceType)) { if (errorCollector) { errorCollector({ message: `Invalid source type: ${sourceType} for relationship type: ${relationshipType}. Valid source types are: ${mapping.source.join(", ")}.`, code: "custom", path: ["source_ref"], input: { relationship_type: relationshipType, source_type: sourceType, target_type: targetType } }); } return false; } if (!mapping.target.includes(targetType)) { if (errorCollector) { errorCollector({ message: `Invalid target type: ${targetType} for relationship type: ${relationshipType}. Valid target types are: ${mapping.target.join(", ")}.`, code: "custom", path: ["target_ref"], input: { relationship_type: relationshipType, source_type: sourceType, target_type: targetType } }); } return false; } if (relationshipType === "uses" && invalidUsesRelationships.some(([s, t]) => s === sourceType && t === targetType)) { if (errorCollector) { errorCollector({ message: `Invalid "uses" relationship: source (${sourceType}) and target (${targetType}) cannot both be "malware" or "tool".`, code: "custom", path: ["relationship_type"], input: { relationship_type: relationshipType, source_type: sourceType, target_type: targetType } }); } return false; } if (relationshipType === "revoked-by" && sourceType !== targetType) { if (errorCollector) { errorCollector({ message: `Invalid "revoked-by" relationship: source (${sourceType}) and target (${targetType}) must be of the same type.`, code: "custom", path: ["relationship_type"], input: { relationship_type: relationshipType, source_type: sourceType, target_type: targetType } }); } return false; } return true; } var allRelationships = stixTypeSchema.options.flatMap( (source) => stixTypeSchema.options.flatMap( (target) => relationshipTypeSchema.options.map((relType) => ({ sourceType: source, relationshipType: relType, targetType: target })) ) ); var invalidRelationships = allRelationships.filter( (rel) => !isValidRelationship(rel.sourceType, rel.relationshipType, rel.targetType) ); function createRelationshipValidationRefinement() { return (ctx) => { const [sourceType] = ctx.value.source_ref.split("--"); const [targetType] = ctx.value.target_ref.split("--"); isValidRelationship(sourceType, ctx.value.relationship_type, targetType, (issue) => { ctx.issues.push(issue); }); }; } var extensibleRelationshipSchema = attackBaseRelationshipObjectSchema.extend({ id: createStixIdValidator("relationship"), type: createStixTypeValidator("relationship"), relationship_type: relationshipTypeSchema, description: descriptionSchema.optional(), source_ref: stixIdentifierSchema.meta({ description: "The ID of the source (from) object." }), target_ref: stixIdentifierSchema.meta({ description: "The ID of the target (to) object." }), x_mitre_modified_by_ref: xMitreModifiedByRefSchema, x_mitre_log_source_channel: z6.string().nonempty().optional() }).omit({ name: true, x_mitre_version: true }).strict(); var relationshipSchema = extensibleRelationshipSchema.check((ctx) => { createRelationshipValidationRefinement()(ctx); createFoundInRelationshipRefinement()(ctx); }); // src/schemas/sdo/stix-bundle.schema.ts var schemaMap = { get malware() { return malwareSchema; }, get "x-mitre-asset"() { return assetSchema; }, get campaign() { return campaignSchema; }, get "x-mitre-collection"() { return collectionSchema; }, get "x-mitre-data-component"() { return dataComponentSchema; }, get "x-mitre-data-source"() { return dataSourceSchema; }, get "x-mitre-detection-strategy"() { return detectionStrategySchema; }, get "x-mitre-log-source"() { return logSourceSchema; }, get "x-mitre-analytic"() { return analyticSchema; }, get identity() { return identitySchema; }, get "x-mitre-matrix"() { return matrixSchema; }, get tool() { return toolSchema; }, get "x-mitre-tactic"() { return tacticSchema; }, get "attack-pattern"() { return techniqueSchema; }, get "intrusion-set"() { return groupSchema; }, get "course-of-action"() { return mitigationSchema; }, get relationship() { return relationshipSchema; }, get "marking-definition"() { return markingDefinitionSchema; } }; var attackObjectsSchema = z7.array( z7.object({ // Basic structure validation to ensure we have a type field type: z7.string({ error: (issue) => { return issue.code === "invalid_type" ? "Object 'type' must be a string" : "The 'type' property is invalid or missing"; } }), id: z7.string({ error: (issue) => { return issue.code === "invalid_type" ? "Object 'id' must be a string" : "The 'id' property is invalid or missing"; } }) }).loose().check((ctx) => { const type = ctx.value.type; const schema = schemaMap[type]; if (!schema) { ctx.issues.push({ code: "custom", message: `Unknown STIX type: ${type}`, path: ["type"], input: ctx.value.type }); return; } try { schema.parse(ctx.value); } catch (error) { if (error instanceof z7.ZodError) { error.issues.forEach((issue) => { ctx.issues.push(issue); }); } else { ctx.issues.push({ code: "custom", message: `Validation error: ${error instanceof Error ? error.message : String(error)}`, input: ctx.value // TODO this might be too much information: how can we filter down to just the relevant part? }); } } }) ).min(1, { message: "The STIX bundle must contain at least one object." }); var extensibleStixBundleSchema = z7.object({ id: createStixIdValidator("bundle"), type: createStixTypeValidator("bundle"), spec_version: z7.literal(stixSpecVersionSchema.enum["2.1"]).meta({ description: "Only STIX 2.1 specification version is allowed" }), objects: attackObjectsSchema }).strict(); var validateFirstBundleObject = createFirstBundleObjectRefinement(); var stixBundleSchema = extensibleStixBundleSchema.check((ctx) => { validateFirstBundleObject(ctx); }); // src/refinements/index.ts function createFirstAliasRefinement() { return (ctx) => { if (ctx.value.aliases && ctx.value.aliases.length > 0) { if (ctx.value.aliases[0] !== ctx.value.name) { ctx.issues.push({ code: "custom", message: "The first alias must match the object's name.", path: ["aliases"], input: ctx.value.aliases }); } } }; } function createFirstXMitreAliasRefinement() { return (ctx) => { if (ctx.value.x_mitre_aliases && ctx.value.x_mitre_aliases.length > 0) { if (ctx.value.x_mitre_aliases[0] !== ctx.value.name) { ctx.issues.push({ code: "custom", message: "The first alias must match the object's name.", path: ["x_mitre_aliases"], input: ctx.value.x_mitre_aliases }); } } }; } function createCitationsRefinement() { return (ctx) => { const { external_references, x_mitre_first_seen_citation, x_mitre_last_seen_citation } = ctx.value; const extractCitationNames = (citations) => { const matches = citations.match(/\(Citation: ([^)]+)\)/g); return matches ? matches.map((match) => match.slice(10, -1).trim()) : []; }; const validateCitationString = (citations, path) => { const citationNames = extractCitationNames(citations); citationNames.forEach((citationName, index) => { const citationExists = external_references.some((ref) => ref.source_name === citationName); if (!citationExists) { ctx.issues.push({ code: "custom", message: `Citation ${citationName} not found in external_references.`, path: [...path, index], input: citationName }); } }); if (!citations.match(/^(\(Citation: [^)]+\))+$/)) { ctx.issues.push({ code: "custom", message: "Citations must be in the format '(Citation: Name1)(Citation: Name2)...' without any separators.", path, input: citations }); } }; if (x_mitre_first_seen_citation) { validateCitationString(x_mitre_first_seen_citation, ["x_mitre_first_seen_citation"]); } if (x_mitre_last_seen_citation) { validateCitationString(x_mitre_last_seen_citation, ["x_mitre_last_seen_citation"]); } }; } function createFirstBundleObjectRefinement() { return (ctx) => { if (ctx.value.objects.length > 0) { const firstObject = ctx.value.objects[0]; if (firstObject.type !== "x-mitre-collection") { ctx.issues.push({ code: "custom", message: "The first object in the 'objects' array must be of type 'x-mitre-collection'", path: ["objects", 0, "type"], input: firstObject.type }); } } }; } function createAttackIdInExternalReferencesRefinement() { return (ctx) => { if (!ctx.value.external_references || !Array.isArray(ctx.value.external_references) || ctx.value.external_references.length === 0) { ctx.issues.push({ code: "custom", message: "At least one external reference with an ATT&CK ID is required.", path: ["external_references"], input: ctx.value.external_references }); return; } const attackIdEntry = ctx.value.external_references[0]; if (!attackIdEntry || typeof attackIdEntry !== "object") { ctx.issues.push({ code: "custom", message: "First external reference must be a valid object.", path: ["external_references", 0], input: attackIdEntry }); return; } if (!attackIdEntry.external_id || typeof attackIdEntry.external_id !== "string") { ctx.issues.push({ code: "custom", message: "ATT&CK ID must be defined in the first external_references entry.", path: ["external_references", 0, "external_id"], input: attackIdEntry.external_id }); return; } const idPattern = ctx.value.x_mitre_is_subtechnique ? attackIdPatterns.subtechnique : attackIdPatterns.technique; const message = ctx.value.x_mitre_is_subtechnique ? `The first external_reference must match the ATT&CK ID format ${attackIdExamples.subtechnique}.` : `The first external_reference must match the ATT&CK ID format ${attackIdExamples.technique}.`; if (!idPattern.test(attackIdEntry.external_id)) { ctx.issues.push({ code: "custom", message, path: ["external_references", 0, "external_id"], input: attackIdEntry.external_id }); } if (!attackIdEntry.source_name || attackIdEntry.source_name !== "mitre-attack") { ctx.issues.push({ code: "custom", message: 'The first external_reference must have source_name set to "mitre-attack".', path: ["external_references", 0, "source_name"], input: attackIdEntry.source_name }); } }; } function createEnterpriseOnlyPropertiesRefinement() { return (ctx) => { const inEnterpriseDomain = ctx.value.x_mitre_domains.includes( attackDomainSchema.enum["enterprise-attack"] ); const tactics = ctx.value.kill_chain_phases?.map((tactic) => tactic.phase_name) || []; function validateEnterpriseOnlyField(fieldName, value, requiredTactic = null) { if (value !== void 0) { if (!inEnterpriseDomain) { ctx.issues.push({ code: "custom", message: `${fieldName} is only supported in the 'enterprise-attack' domain.`, path: [fieldName], input: value }); } else if (requiredTactic && ctx.value.kill_chain_phases !== void 0 && !tactics.includes(requiredTactic)) { ctx.issues.push({ code: "custom", message: `${fieldName} is only supported in the ${requiredTactic} tactic.`, path: [fieldName], input: value }); } } } validateEnterpriseOnlyField( "x_mitre_system_requirements", ctx.value.x_mitre_system_requirements ); validateEnterpriseOnlyField( "x_mitre_permissions_required", ctx.value.x_mitre_permissions_required, "privilege-escalation" ); validateEnterpriseOnlyField( "x_mitre_effective_permissions", ctx.value.x_mitre_effective_permissions, "privilege-escalation" ); validateEnterpriseOnlyField( "x_mitre_defense_bypassed", ctx.value.x_mitre_defense_bypassed, "defense-evasion" ); validateEnterpriseOnlyField( "x_mitre_remote_support", ctx.value.x_mitre_remote_support, "execution" ); validateEnterpriseOnlyField("x_mitre_impact_type", ctx.value.x_mitre_impact_type, "impact"); if (ctx.value.x_mitre_data_sources && inEnterpriseDomain && ctx.value.x_mitre_domains.includes(attackDomainSchema.enum["mobile-attack"])) { ctx.issues.push({ code: "custom", message: "x_mitre_data_sources is not supported in the 'mobile-attack' domain.", path: ["x_mitre_data_sources"], input: ctx.value.x_mitre_data_sources }); } }; } function createMobileOnlyPropertiesRefinement() { return (ctx) => { const inMobileDomain = ctx.value.x_mitre_domains.includes( attackDomainSchema.enum["mobile-attack"] ); if (ctx.value.x_mitre_tactic_type?.length && !inMobileDomain) { ctx.issues.push({ code: "custom", message: "x_mitre_tactic_type is only supported in the 'mobile-attack' domain.", path: ["x_mitre_tactic_type"], input: ctx.value.x_mitre_tactic_type }); } if (ctx.value.x_mitre_data_sources && inMobileDomain) { ctx.issues.push({ code: "custom", message: "x_mitre_data_sources is not supported in the 'mobile-attack' domain.", path: ["x_mitre_data_sources"], input: ctx.value.x_mitre_data_sources }); } }; } function createFoundInRelationshipRefinement() { return (ctx) => { if (ctx.value.relationship_type === "found-in" && !ctx.value.x_mitre_log_source_channel) { ctx.issues.push({ code: "custom", message: "x_mitre_log_source_channel must be defined if relationship_type is 'found-in'", path: ["x_mitre_log_source_channel"], input: { relationship_type: ctx.value.relationship_type, x_mitre_log_source_channel: ctx.value.x_mitre_log_source_channel } }); } if (ctx.value.relationship_type !== "found-in" && ctx.value.x_mitre_log_source_channel) { ctx.issues.push({ code: "custom", message: "x_mitre_log_source_channel can only be defined if relationship_type is 'found-in'", path: ["x_mitre_log_source_channel"], input: { relationship_type: ctx.value.relationship_type, x_mitre_log_source_channel: ctx.value.x_mitre_log_source_channel } }); } }; } export { createFirstAliasRefinement, createFirstXMitreAliasRefinement, createCitationsRefinement, createFirstBundleObjectRefinement, createAttackIdInExternalReferencesRefinement, createEnterpriseOnlyPropertiesRefinement, createMobileOnlyPropertiesRefinement, createFoundInRelationshipRefinement, xMitreFirstSeenCitationSchema, xMitreLastSeenCitationSchema, extensibleCampaignSchema, campaignSchema, extensibleGroupSchema, groupSchema, stixFileType, stixArtifactType, extensibleMalwareSchema, malwareSchema, xMitreNetworkRequirementsSchema, xMitreEffectivePermissionsSchema, xMitreImpactTypeSchema, xMitreSystemRequirementsSchema, xMitreRemoteSupportSchema, xMitrePermissionsRequiredSchema, xMitreDataSourceSchema, xMitreDataSourcesSchema, xMitreIsSubtechniqueSchema, xMitreTacticTypeSchema, xMitreDefenseBypassesSchema, xMitreDetectionSchema, extensibleTechniqueSchema, techniqueSchema, extensibleToolSchema, toolSchema, relationshipTypeSchema, validRelationshipObjectTypes, isValidRelationship, invalidRelationships, createRelationshipValidationRefinement, extensibleRelationshipSchema, relationshipSchema, attackObjectsSchema, extensibleStixBundleSchema, stixBundleSchema };