1- from typing import List , Optional , Dict , Any
1+ """Pydantic models for ATT&CK STIX objects."""
2+
3+ from dataclasses import dataclass
4+ from typing import Any , Dict , List , Optional
5+
26from pydantic import BaseModel , Field , model_validator
37
8+
9+ @dataclass (frozen = True )
10+ class LoadedStix :
11+ """Container for parsed STIX objects and detected spec version."""
12+
13+ spec_version : str | None
14+ objects : list [Any ]
15+
16+ class LogSourceReference (BaseModel ):
17+ """A log source reference used by an analytic."""
18+
19+ x_mitre_data_component_ref : str
20+ name : str
21+ channel : str
22+
23+
24+ class MutableElement (BaseModel ):
25+ """Environment-specific analytic tuning knobs."""
26+
27+ field : str
28+ description : str
29+
30+
431class ExternalReference (BaseModel ):
532 """STIX external reference entry."""
633
@@ -27,6 +54,21 @@ class STIXCore(BaseModel):
2754
2855 @model_validator (mode = 'before' )
2956 def extract_common_fields (cls , values ):
57+ """Extract common fields from the input dictionary.
58+
59+ This method processes the input dictionary to extract and set common fields,
60+ such as the URL from the first external reference, if available.
61+
62+ Parameters
63+ ----------
64+ values : dict
65+ The input dictionary containing STIX object data.
66+
67+ Returns
68+ -------
69+ dict
70+ The updated dictionary with extracted common fields.
71+ """
3072 external_references = values .get ('external_references' )
3173 if external_references and len (external_references ) > 0 :
3274 first_ref = external_references [0 ]
@@ -36,6 +78,18 @@ def extract_common_fields(cls, values):
3678
3779 @classmethod
3880 def extract_external_id (cls , external_references : List [ExternalReference ]):
81+ """Extract the external ID from the first external reference.
82+
83+ Parameters
84+ ----------
85+ external_references : List[ExternalReference]
86+ A list of external references associated with the STIX object.
87+
88+ Returns
89+ -------
90+ str or None
91+ The external ID from the first external reference, or None if not available.
92+ """
3993 if external_references and len (external_references ) > 0 :
4094 return external_references [0 ].external_id
4195 return None
@@ -63,6 +117,18 @@ class Technique(STIXCore):
63117
64118 @model_validator (mode = 'before' )
65119 def extract_phase_name (cls , values : Dict [str , Any ]):
120+ """Extract phase names from the technique field.
121+
122+ Parameters
123+ ----------
124+ values : Dict[str, Any]
125+ The input dictionary containing technique data.
126+
127+ Returns
128+ -------
129+ Dict[str, Any]
130+ The updated dictionary with extracted phase names.
131+ """
66132 if 'kill_chain_phases' in values :
67133 kill_chain_phases = values ['kill_chain_phases' ]
68134 phase_names = [phase ['phase_name' ] for phase in kill_chain_phases if 'phase_name' in phase ]
@@ -71,6 +137,11 @@ def extract_phase_name(cls, values: Dict[str, Any]):
71137
72138 @model_validator (mode = 'after' )
73139 def set_technique_id (self ):
140+ """Set the technique ID based on the external references.
141+
142+ This method extracts the external ID from the first external reference
143+ and assigns it to the `technique_id` attribute.
144+ """
74145 self .technique_id = self .extract_external_id (self .external_references )
75146 return self
76147
@@ -84,6 +155,11 @@ class Mitigation(STIXCore):
84155
85156 @model_validator (mode = 'after' )
86157 def set_mitigation_id (self ):
158+ """Set the mitigation ID based on the external references.
159+
160+ This method extracts the external ID from the first external reference
161+ and assigns it to the `mitigation_id` attribute.
162+ """
87163 self .mitigation_id = self .extract_external_id (self .external_references )
88164 return self
89165
@@ -98,6 +174,11 @@ class Group(STIXCore):
98174
99175 @model_validator (mode = 'after' )
100176 def set_group_id (self ):
177+ """Set the group ID based on the external references.
178+
179+ This method extracts the external ID from the first external reference
180+ and assigns it to the `group_id` attribute.
181+ """
101182 self .group_id = self .extract_external_id (self .external_references )
102183 return self
103184
@@ -115,6 +196,11 @@ class Software(STIXCore):
115196
116197 @model_validator (mode = 'after' )
117198 def set_software_id (self ):
199+ """Set the software ID based on the external references.
200+
201+ This method extracts the external ID from the first external reference
202+ and assigns it to the `software_id` attribute.
203+ """
118204 self .software_id = self .extract_external_id (self .external_references )
119205 return self
120206
@@ -144,6 +230,11 @@ class Tactic(STIXCore):
144230
145231 @model_validator (mode = 'after' )
146232 def set_tactic_id (self ):
233+ """Set the tactic ID based on the external references.
234+
235+ This method extracts the external ID from the first external reference
236+ and assigns it to the `tactic_id` attribute.
237+ """
147238 self .tactic_id = self .extract_external_id (self .external_references )
148239 return self
149240
@@ -157,6 +248,11 @@ class Matrix(STIXCore):
157248
158249 @model_validator (mode = 'after' )
159250 def set_matrix_id (self ):
251+ """Set the matrix ID based on the external references.
252+
253+ This method extracts the external ID from the first external reference
254+ and assigns it to the `matrix_id` attribute.
255+ """
160256 self .matrix_id = self .extract_external_id (self .external_references )
161257 return self
162258
@@ -199,6 +295,11 @@ class Campaign(STIXCore):
199295
200296 @model_validator (mode = 'after' )
201297 def set_campaign_id (self ):
298+ """Set the campaign ID based on the external references.
299+
300+ This method extracts the external ID from the first external reference
301+ and assigns it to the `campaign_id` attribute.
302+ """
202303 self .campaign_id = self .extract_external_id (self .external_references )
203304 return self
204305
@@ -219,6 +320,18 @@ class GroupTechnique(Group):
219320
220321 @model_validator (mode = 'before' )
221322 def extract_phase_name (cls , values : Dict [str , Any ]):
323+ """Extract phase names from the group field.
324+
325+ Parameters
326+ ----------
327+ values : Dict[str, Any]
328+ The input dictionary containing group data.
329+
330+ Returns
331+ -------
332+ Dict[str, Any]
333+ The updated dictionary with extracted phase names.
334+ """
222335 if 'tactic' in values :
223336 kill_chain_phases = values ['tactic' ]
224337 phase_names = [phase ['phase_name' ] for phase in kill_chain_phases if 'phase_name' in phase ]
@@ -231,3 +344,47 @@ class STIXLocalPaths(BaseModel):
231344 enterprise : Optional [str ] = Field (None , description = "Path to the local enterprise-attack directory or JSON file." )
232345 mobile : Optional [str ] = Field (None , description = "Path to the local mobile-attack directory or JSON file." )
233346 ics : Optional [str ] = Field (None , description = "Path to the local ics-attack directory or JSON file." )
347+
348+
349+ class DetectionStrategy (STIXCore ):
350+ """ATT&CK Detection Strategy model (x-mitre-detection-strategy)."""
351+
352+ detection_strategy : str = Field (..., alias = "name" )
353+ analytic_refs : List [str ] = Field (default_factory = list , alias = "x_mitre_analytic_refs" )
354+
355+
356+ class Analytic (STIXCore ):
357+ """ATT&CK Analytic model (x-mitre-analytic)."""
358+
359+ analytic : str = Field (..., alias = "name" )
360+ analytic_description : Optional [str ] = Field (None , alias = "description" )
361+ platforms : Optional [List [str ]] = Field (None , alias = "x_mitre_platforms" )
362+ log_source_references : Optional [List [LogSourceReference ]] = Field (
363+ None , alias = "x_mitre_log_source_references"
364+ )
365+ mutable_elements : Optional [List [MutableElement ]] = Field (None , alias = "x_mitre_mutable_elements" )
366+
367+ pydantic_model_mapping = {
368+ "techniques" : Technique ,
369+ "data-component" : DataComponent ,
370+ "mitigations" : Mitigation ,
371+ "groups" : Group ,
372+ "malware" : Software ,
373+ "tools" : Software ,
374+ "tool" : Software ,
375+ "data-source" : DataSource ,
376+ "relationships" : Relationship ,
377+ "tactics" : Tactic ,
378+ "matrix" : Matrix ,
379+ "identity" : Identity ,
380+ "marking-definition" : MarkingDefinition ,
381+ "campaigns" : Campaign ,
382+ "campaign" : Campaign ,
383+ "attack-pattern" : Technique ,
384+ "course-of-action" : Mitigation ,
385+ "intrusion-set" : Group ,
386+ "x-mitre-data-source" : DataSource ,
387+ "x-mitre-data-component" : DataComponent ,
388+ "x-mitre-detection-strategy" : DetectionStrategy ,
389+ "x-mitre-analytic" : Analytic ,
390+ }
0 commit comments