Skip to content

Commit 89c2230

Browse files
fix findings
1 parent fb8373a commit 89c2230

File tree

7 files changed

+405
-359
lines changed

7 files changed

+405
-359
lines changed

bbot_server/models/base.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import logging
22
from hashlib import sha1
3+
from typing import Union
34

45
from bbot.models.pydantic import BBOTBaseModel
6+
from bbot_server.errors import BBOTServerValueError
57
from bbot_server.utils.misc import _sanitize_mongo_query
68

79
log = logging.getLogger("bbot_server.models")
@@ -13,3 +15,33 @@ def model_dump(self, *args, mode="json", exclude_none=True, **kwargs):
1315

1416
def sha1(self, data: str) -> str:
1517
return sha1(data.encode()).hexdigest()
18+
19+
20+
class BaseScore:
21+
"""Base class for mapping string levels to numeric scores."""
22+
23+
levels: dict = {}
24+
name: str = "score"
25+
26+
@classmethod
27+
def to_score(cls, value: Union[str, int]) -> int:
28+
"""Convert a level to its numeric score."""
29+
if isinstance(value, int):
30+
if value not in cls.levels.values():
31+
raise BBOTServerValueError(f'Invalid {cls.name} score: "{value}". Must be between 1 and 5.')
32+
return value
33+
if isinstance(value, str):
34+
value = value.upper()
35+
if value not in cls.levels:
36+
raise BBOTServerValueError(
37+
f'Invalid {cls.name} string: "{value}". Must be one of {list(cls.levels.keys())}'
38+
)
39+
return cls.levels[value]
40+
41+
@classmethod
42+
def to_str(cls, score: int) -> str:
43+
"""Convert a numeric score to its string equivalent."""
44+
for level, value in cls.levels.items():
45+
if value == score:
46+
return level
47+
raise BBOTServerValueError(f"Invalid {cls.name} score: {score}. Must be between 1 and 5.")

bbot_server/modules/findings/findings_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ async def compute_stats(self, asset, stats):
247247
max_severity_score = max([asset.finding_max_severity_score, finding_stats.get("max_severity_score", 0)])
248248
finding_stats["max_severity_score"] = max_severity_score
249249
if max_severity_score > 0:
250-
max_severity = SeverityScore.to_severity(max_severity_score)
250+
max_severity = SeverityScore.to_str(max_severity_score)
251251
else:
252252
max_severity = None
253253
finding_stats["max_severity"] = max_severity
@@ -304,8 +304,8 @@ async def _insert_or_update_finding(self, finding: Finding, asset, event=None):
304304
{
305305
"$set": {
306306
"modified": self.helpers.utc_now(),
307-
"confidence": finding.confidence,
308-
"severity": finding.severity,
307+
"confidence": finding.confidence_score,
308+
"severity_score": finding.severity_score,
309309
}
310310
},
311311
)
@@ -318,7 +318,7 @@ async def _insert_or_update_finding(self, finding: Finding, asset, event=None):
318318
severity_scores = {SeverityScore.to_score(severity) for severity in finding_severities}
319319
if severity_scores:
320320
asset.finding_max_severity_score = max(severity_scores)
321-
asset.finding_max_severity = SeverityScore.to_severity(asset.finding_max_severity_score)
321+
asset.finding_max_severity = SeverityScore.to_str(asset.finding_max_severity_score)
322322
else:
323323
asset.finding_max_severity_score = 0
324324
asset.finding_max_severity = None

bbot_server/modules/findings/findings_models.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
from typing import Annotated, Optional
12
from pydantic import Field, computed_field
2-
from typing import Annotated, Optional, Union
33

4-
from bbot_server.errors import BBOTServerValueError
4+
from bbot_server.models.base import BaseScore
55
from bbot_server.models.asset_models import BaseAssetFacet
66

77
# Severity levels as constants
88
SEVERITY_LEVELS = {"INFO": 1, "LOW": 2, "MEDIUM": 3, "HIGH": 4, "CRITICAL": 5}
99

10+
# Confidence levels as constants
11+
CONFIDENCE_LEVELS = {"UNKNOWN": 1, "LOW": 2, "MODERATE": 3, "HIGH": 4, "CONFIRMED": 5}
12+
1013
# severity colors for rich, etc. (bash color names)
1114
SEVERITY_COLORS = {
1215
1: "deep_sky_blue1", # INFO = blue
@@ -17,31 +20,18 @@
1720
}
1821

1922

20-
class SeverityScore:
23+
class SeverityScore(BaseScore):
2124
"""Maps severity levels to numeric scores and provides conversion methods."""
2225

23-
@classmethod
24-
def to_score(cls, severity: Union[str, int]) -> int:
25-
"""Convert a severity level to its numeric score."""
26-
if isinstance(severity, int):
27-
if severity not in SEVERITY_LEVELS.values():
28-
raise BBOTServerValueError(f'Invalid severity score: "{severity}". Must be between 1 and 5.')
29-
return severity
30-
if isinstance(severity, str):
31-
severity = severity.upper()
32-
if severity not in SEVERITY_LEVELS:
33-
raise BBOTServerValueError(
34-
f'Invalid severity string: "{severity}". Must be one of {list(SEVERITY_LEVELS.keys())}'
35-
)
36-
return SEVERITY_LEVELS[severity]
37-
38-
@classmethod
39-
def to_severity(cls, score: int) -> str:
40-
"""Convert a numeric score to its string equivalent."""
41-
for level, value in SEVERITY_LEVELS.items():
42-
if value == score:
43-
return level
44-
raise BBOTServerValueError(f"Invalid severity score: {score}. Must be between 1 and 5.")
26+
levels = SEVERITY_LEVELS
27+
name = "severity"
28+
29+
30+
class ConfidenceScore(BaseScore):
31+
"""Maps confidence levels to numeric scores and provides conversion methods."""
32+
33+
levels = CONFIDENCE_LEVELS
34+
name = "confidence"
4535

4636

4737
class Finding(BaseAssetFacet):
@@ -53,11 +43,12 @@ class Finding(BaseAssetFacet):
5343
ge=1,
5444
le=5,
5545
)
56-
confidence: Annotated[int, "indexed"] = Field(
57-
description="Confidence level of the vulnerability (1-5)",
46+
confidence_score: Annotated[int, "indexed"] = Field(
47+
description="Numeric confidence score of the vulnerability (1-5)",
5848
ge=1,
5949
le=5,
6050
default=1,
51+
alias="confidence",
6152
)
6253
temptation: Optional[Annotated[int, "indexed"]] = Field(
6354
description="Likelihood of an attacker taking interest in this finding (1-5)",
@@ -75,6 +66,10 @@ def __init__(self, **kwargs):
7566
severity = kwargs.pop("severity", None)
7667
if severity is not None:
7768
kwargs["severity_score"] = SeverityScore.to_score(severity)
69+
# convert confidence to confidence_score
70+
confidence = kwargs.pop("confidence", None)
71+
if confidence is not None:
72+
kwargs["confidence_score"] = ConfidenceScore.to_score(confidence)
7873
super().__init__(**kwargs)
7974

8075
@computed_field
@@ -83,7 +78,15 @@ def severity(self) -> str:
8378
"""
8479
The string version of the severity score, e.g. 3 -> "MEDIUM", 4 -> "HIGH", etc.
8580
"""
86-
return SeverityScore.to_severity(self.severity_score)
81+
return SeverityScore.to_str(self.severity_score)
82+
83+
@computed_field
84+
@property
85+
def confidence(self) -> str:
86+
"""
87+
The string version of the confidence score, e.g. 1 -> "UNKNOWN", 5 -> "CONFIRMED", etc.
88+
"""
89+
return ConfidenceScore.to_str(self.confidence_score)
8790

8891
@computed_field
8992
@property

0 commit comments

Comments
 (0)