@mitre-attack/attack-data-model
Version:
A TypeScript API for the MITRE ATT&CK data model
234 lines (231 loc) • 7.64 kB
JavaScript
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
};