@@ -44,7 +44,7 @@ class DetectionTags(BaseModel):
44
44
asset_type : AssetType = Field (...)
45
45
group : list [str ] = []
46
46
47
- mitre_attack_id : List [MITRE_ATTACK_ID_TYPE ] = []
47
+ mitre_attack_id : list [MITRE_ATTACK_ID_TYPE ] = []
48
48
nist : list [NistCategory ] = []
49
49
50
50
product : list [SecurityContentProductName ] = Field (..., min_length = 1 )
@@ -165,6 +165,39 @@ def addAttackEnrichments(cls, v:list[MitreAttackEnrichment], info:ValidationInfo
165
165
return enrichments
166
166
"""
167
167
168
+ @field_validator ("mitre_attack_id" , mode = "after" )
169
+ @classmethod
170
+ def sameTypeAndSubtypeNotPresent (
171
+ cls , mitre_ids : list [MITRE_ATTACK_ID_TYPE ]
172
+ ) -> list [MITRE_ATTACK_ID_TYPE ]:
173
+ id_types : list [str ] = [
174
+ f"{ mitre_id } ." for mitre_id in mitre_ids if "." not in mitre_id
175
+ ]
176
+ id_subtypes : list [MITRE_ATTACK_ID_TYPE ] = [
177
+ mitre_id for mitre_id in mitre_ids if "." in mitre_id
178
+ ]
179
+ subtype_and_parent_exist_exceptions : list [ValueError ] = []
180
+
181
+ for id_subtype in id_subtypes :
182
+ for id_type in id_types :
183
+ if id_subtype .startswith (id_type ):
184
+ subtype_and_parent_exist_exceptions .append (
185
+ ValueError (
186
+ f" Tactic : { id_type .split ('.' )[0 ]} \n "
187
+ f" Subtactic: { id_subtype } \n "
188
+ )
189
+ )
190
+
191
+ if len (subtype_and_parent_exist_exceptions ):
192
+ error_string = "\n " .join (
193
+ str (e ) for e in subtype_and_parent_exist_exceptions
194
+ )
195
+ raise ValueError (
196
+ f"Overlapping MITRE Attack ID Tactics and Subtactics may not be defined:\n { error_string } "
197
+ )
198
+
199
+ return mitre_ids
200
+
168
201
@field_validator ("analytic_story" , mode = "before" )
169
202
@classmethod
170
203
def mapStoryNamesToStoryObjects (
0 commit comments