UNPKG

@mitre-attack/attack-data-model

Version:

A TypeScript API for the MITRE ATT&CK data model

234 lines (231 loc) 7.64 kB
import { attackBaseRelationshipObjectSchema } from "./chunk-ZHQVMLOZ.js"; import { descriptionSchema } from "./chunk-DNIVZ2SM.js"; import { xMitreModifiedByRefSchema } from "./chunk-U55YRJAX.js"; import { createStixIdValidator, stixIdentifierSchema } from "./chunk-E3OY6DRE.js"; import { createStixTypeValidator, stixTypeSchema } from "./chunk-PFSYT437.js"; // src/schemas/sro/relationship.schema.ts import { z } from "zod/v4"; var supportedRelationshipTypes = [ "uses", "mitigates", "subtechnique-of", "detects", "attributed-to", "targets", "revoked-by" ]; var relationshipTypeSchema = z.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"] ]; 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: [ // TODO remove DC --<detects>--> Technique in spec release 4.x stixTypeSchema.enum["x-mitre-data-component"], 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 } }; 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) => { if (!ctx.value.relationship_type || !ctx.value.source_ref || !ctx.value.target_ref) { return; } 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({ code: "custom", message: issue.message, path: issue.path, input: issue.input }); }); }; } var relationshipBaseSchema = 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 }).omit({ name: true, x_mitre_version: true }).strict(); var relationshipChecks = (ctx) => { createRelationshipValidationRefinement()(ctx); if (!ctx.value.source_ref || !ctx.value.target_ref || !ctx.value.relationship_type) return; const [sourceType] = ctx.value.source_ref.split("--"); if (sourceType === "x-mitre-data-component" && ctx.value.relationship_type === "detects" && ctx.value.target_ref.startsWith("attack-pattern--")) { console.warn( "DEPRECATION WARNING: x-mitre-data-component -> detects -> attack-pattern relationships are deprecated" ); } }; var relationshipSchema = relationshipBaseSchema.check(relationshipChecks); var relationshipPartialSchema = relationshipBaseSchema.partial().check(relationshipChecks); export { relationshipTypeSchema, validRelationshipObjectTypes, isValidRelationship, invalidRelationships, createRelationshipValidationRefinement, relationshipBaseSchema, relationshipChecks, relationshipSchema, relationshipPartialSchema };