Skip to content

Commit 73bc0a3

Browse files
committed
feat(CWE): add parent and children
1 parent 99825b1 commit 73bc0a3

File tree

1 file changed

+78
-15
lines changed

1 file changed

+78
-15
lines changed

codesectools/shared/cwe.py

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,31 @@ class CWE:
2020
id (int): The CWE identifier.
2121
name (str): The name of the weakness.
2222
description (str): A description of the weakness.
23+
parent (CWE | None): The parent CWE weakness, if any.
24+
children (set[CWE]): A set of child CWE weaknesses.
2325
2426
"""
2527

26-
def __init__(self, id: int, name: str, description: str) -> None:
28+
def __init__(
29+
self,
30+
id: int,
31+
name: str,
32+
description: str,
33+
parent: Self | None = None,
34+
children: set[Self] | None = None,
35+
) -> None:
2736
"""Initialize a CWE instance.
2837
2938
Args:
3039
id: The CWE identifier.
3140
name: The name of the weakness.
3241
description: A description of the weakness.
42+
parent: The parent CWE weakness, if any.
43+
children: A set of child CWE weaknesses, if any.
3344
3445
"""
46+
if children is None:
47+
children = set()
3548
self.id = id
3649
if r := re.search(r"\('(.*)'\)", name):
3750
self.name = r.group(1)
@@ -40,6 +53,8 @@ def __init__(self, id: int, name: str, description: str) -> None:
4053
self.name = self.full_name = name
4154

4255
self.description = description
56+
self.parent = parent
57+
self.children = children or set()
4358

4459
def __eq__(self, other: Self | int) -> bool:
4560
"""Compare this CWE with another object for equality.
@@ -72,6 +87,31 @@ def __repr__(self) -> str:
7287
"""
7388
return f"{self.__class__.__name__}(id={self.id})"
7489

90+
def extend(self, distance: int = 1) -> set[Self]:
91+
"""Retrieve the set of related CWEs within a specified distance in the hierarchy.
92+
93+
Recursively finds parent and child CWEs up to the given distance level.
94+
Includes the current CWE in the returned set.
95+
96+
Args:
97+
distance: The number of levels to traverse up (parents) and down (children).
98+
Defaults to 1.
99+
100+
Returns:
101+
A set of CWE objects including the self and related weaknesses.
102+
103+
"""
104+
cwes: set[Self] = set([self])
105+
for _ in range(distance):
106+
new_cwes = cwes.copy()
107+
for cwe in cwes:
108+
if cwe.parent:
109+
new_cwes.add(cwe.parent)
110+
for child in cwe.children:
111+
new_cwes.add(child)
112+
cwes = new_cwes.copy()
113+
return cwes
114+
75115

76116
class CWEsCollection:
77117
"""Manage the collection of all CWEs.
@@ -136,24 +176,50 @@ def download(self) -> None:
136176
)
137177
progress.update(task, advance=25)
138178

139-
def load(self) -> list[CWE]:
140-
"""Load CWE data from the CSV file.
179+
def load(self) -> dict[int, CWE]:
180+
"""Load and parse CWE data from cached CSV files.
181+
182+
Reads the CSV files defined in `cwes_data`, instantiates `CWE` objects,
183+
and establishes parent-child relationships based on the "Related Weaknesses" field.
141184
142185
Returns:
143-
A list of CWE objects.
186+
A dictionary mapping CWE IDs (int) to `CWE` objects.
144187
145188
"""
146-
cwes = []
189+
cwes = {}
190+
cwes_parent = {}
191+
cwes_children = {}
147192
for filename in self.cwes_data.values():
148193
reader = csv.DictReader((self.directory / filename).open(encoding="utf-8"))
149194
for cwe_dict in reader:
150-
cwes.append(
151-
CWE(
152-
id=int(cwe_dict["CWE-ID"]),
153-
name=cwe_dict["Name"],
154-
description=cwe_dict["Description"],
155-
)
195+
cwe_id = int(cwe_dict["CWE-ID"])
196+
197+
cwes[cwe_id] = CWE(
198+
id=cwe_id,
199+
name=cwe_dict["Name"],
200+
description=cwe_dict["Description"],
156201
)
202+
203+
for related in cwe_dict["Related Weaknesses"].split("::"):
204+
if m := re.search(r"NATURE:ChildOf:CWE ID:(\d+):", related):
205+
parent_id = int(m.group(1))
206+
207+
cwes_parent[cwe_id] = parent_id
208+
209+
if cwes_children.get(parent_id):
210+
cwes_children[parent_id].add(cwe_id)
211+
else:
212+
cwes_children[parent_id] = {cwe_id}
213+
214+
break
215+
216+
for cwe_id, cwe in cwes.items():
217+
if p_id := cwes_parent.get(cwe_id):
218+
cwe.parent = cwes.get(p_id, None)
219+
for c_id in cwes_children.get(cwe_id, []):
220+
if child_cwe := cwes.get(c_id):
221+
cwe.children.add(child_cwe)
222+
157223
return cwes
158224

159225
def from_string(self, cwe_string: str) -> CWE:
@@ -181,10 +247,7 @@ def from_id(self, cwe_id: int) -> CWE:
181247
The CWE object if found, otherwise a default CWE object with ID -1.
182248
183249
"""
184-
try:
185-
return self.cwes[self.cwes.index(cwe_id)]
186-
except ValueError:
187-
return self.NOCWE
250+
return self.cwes.get(cwe_id, self.NOCWE)
188251

189252

190253
CWEs = CWEsCollection()

0 commit comments

Comments
 (0)