regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (37) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +12 -5
  3. regscale/core/app/internal/set_permissions.py +58 -27
  4. regscale/integrations/commercial/nessus/scanner.py +2 -0
  5. regscale/integrations/commercial/sonarcloud.py +35 -36
  6. regscale/integrations/commercial/synqly/ticketing.py +51 -0
  7. regscale/integrations/integration_override.py +15 -6
  8. regscale/integrations/scanner_integration.py +163 -35
  9. regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
  10. regscale/models/integration_models/aqua.py +92 -78
  11. regscale/models/integration_models/cisa_kev_data.json +47 -4
  12. regscale/models/integration_models/defenderimport.py +64 -59
  13. regscale/models/integration_models/ecr_models/ecr.py +100 -147
  14. regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
  15. regscale/models/integration_models/ibm.py +29 -47
  16. regscale/models/integration_models/nexpose.py +156 -68
  17. regscale/models/integration_models/prisma.py +46 -66
  18. regscale/models/integration_models/qualys.py +99 -93
  19. regscale/models/integration_models/snyk.py +229 -158
  20. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  21. regscale/models/integration_models/veracode.py +15 -20
  22. regscale/models/integration_models/xray.py +276 -82
  23. regscale/models/regscale_models/control_implementation.py +14 -12
  24. regscale/models/regscale_models/milestone.py +1 -1
  25. regscale/models/regscale_models/rbac.py +22 -0
  26. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/METADATA +1 -1
  27. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/RECORD +37 -36
  28. tests/fixtures/test_fixture.py +58 -2
  29. tests/regscale/core/test_app.py +5 -3
  30. tests/regscale/integrations/test_integration_mapping.py +522 -40
  31. tests/regscale/integrations/test_issue_due_date.py +1 -1
  32. tests/regscale/integrations/test_update_finding_dates.py +336 -0
  33. tests/regscale/models/test_asset.py +406 -50
  34. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/LICENSE +0 -0
  35. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/WHEEL +0 -0
  36. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/entry_points.txt +0 -0
  37. {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/top_level.txt +0 -0
@@ -33,7 +33,7 @@ from regscale.core.app.utils.parser_utils import safe_datetime_str
33
33
  from regscale.integrations.scanner_integration import ScannerIntegration
34
34
  from regscale.models import IssueStatus, Metadata, regscale_models
35
35
  from regscale.models.app_models.mapping import Mapping
36
- from regscale.models.regscale_models import Asset, File, Vulnerability
36
+ from regscale.models.regscale_models import Asset, File, IssueSeverity, Vulnerability
37
37
 
38
38
  logger = logging.getLogger(__name__)
39
39
 
@@ -41,6 +41,10 @@ DT_FORMAT = "%Y-%m-%d"
41
41
 
42
42
 
43
43
  class FlatFileIntegration(ScannerIntegration):
44
+ """
45
+ Flat File Integration
46
+ """
47
+
44
48
  title = "Flat File Integration"
45
49
  # Required fields from ScannerIntegration
46
50
  asset_identifier_field = "name"
@@ -53,13 +57,6 @@ class FlatFileIntegration(ScannerIntegration):
53
57
  finding_severity_map: Optional[dict] = None,
54
58
  **kwargs: Any,
55
59
  ):
56
- """
57
- Initialize the FlatFileIntegration
58
-
59
- :param int plan_id: The plan id
60
- :param str asset_identifier_field: The asset identifier field to use, defaults to "name"
61
- :param dict kwargs: Additional keyword arguments
62
- """
63
60
  self.asset_identifier_field = asset_identifier_field
64
61
  if finding_severity_map:
65
62
  self.finding_severity_map = finding_severity_map
@@ -68,6 +65,7 @@ class FlatFileIntegration(ScannerIntegration):
68
65
  "Critical": regscale_models.IssueSeverity.Critical,
69
66
  "High": regscale_models.IssueSeverity.High,
70
67
  "Medium": regscale_models.IssueSeverity.Moderate,
68
+ "Moderate": regscale_models.IssueSeverity.Moderate,
71
69
  "Low": regscale_models.IssueSeverity.Low,
72
70
  }
73
71
  super().__init__(plan_id=plan_id, **kwargs)
@@ -126,20 +124,12 @@ class FlatFileImporter(ABC):
126
124
  "Critical": regscale_models.IssueSeverity.Critical,
127
125
  "High": regscale_models.IssueSeverity.High,
128
126
  "Medium": regscale_models.IssueSeverity.Moderate,
127
+ "Moderate": regscale_models.IssueSeverity.Moderate,
129
128
  "Low": regscale_models.IssueSeverity.Low,
130
129
  }
131
130
 
132
- # Set the parent_id, parent_module, and plan_id if they are not provided
133
- if "parent_id" not in kwargs and "object_id" in kwargs:
134
- kwargs["parent_id"] = kwargs["object_id"]
135
- kwargs["plan_id"] = kwargs["object_id"]
136
- if kwargs.get("is_component", False):
137
- kwargs["parent_module"] = regscale_models.Component.get_module_string()
138
- else:
139
- kwargs["parent_module"] = regscale_models.SecurityPlan.get_module_string()
131
+ kwargs = self.update_kwargs(kwargs)
140
132
 
141
- if "app" not in kwargs:
142
- kwargs["app"] = Application()
143
133
  # empty generator
144
134
  self.integration_assets: Generator["IntegrationAsset", None, None] = (x for x in [])
145
135
  self.integration_findings: Generator["IntegrationAsset", None, None] = (x for x in [])
@@ -209,7 +199,7 @@ class FlatFileImporter(ABC):
209
199
  flat_int.num_assets_to_process = len(self.data["assets"])
210
200
  if vuln_count:
211
201
  flat_int.num_findings_to_process = vuln_count
212
- elif isinstance(self.data["vulns"], list):
202
+ elif isinstance(self.data["vulns"], list) and not vuln_count:
213
203
  flat_int.num_findings_to_process = len(self.data["vulns"])
214
204
  flat_int.sync_assets(
215
205
  plan_id=self.attributes.plan_id,
@@ -229,6 +219,31 @@ class FlatFileImporter(ABC):
229
219
  )
230
220
  self.clean_up()
231
221
 
222
+ def update_kwargs(self, kwargs: dict) -> dict:
223
+ """
224
+ Update the kwargs with the default values
225
+
226
+ :param dict kwargs: The kwargs to update
227
+ :return: The updated kwargs
228
+ :rtype: dicta
229
+ """
230
+ # Set the parent_id, parent_module, and plan_id if they are not provided
231
+ if "parent_id" not in kwargs and "object_id" in kwargs:
232
+ kwargs["parent_id"] = kwargs["object_id"]
233
+ kwargs["plan_id"] = kwargs["object_id"]
234
+ if kwargs.get("is_component", False):
235
+ kwargs["parent_module"] = regscale_models.Component.get_module_string()
236
+ else:
237
+ kwargs["parent_module"] = regscale_models.SecurityPlan.get_module_string()
238
+
239
+ # if plan id is still not set, set it to the object id
240
+ if "plan_id" not in kwargs or not kwargs["plan_id"]:
241
+ kwargs["plan_id"] = kwargs["object_id"]
242
+
243
+ if "app" not in kwargs:
244
+ kwargs["app"] = Application()
245
+ return kwargs
246
+
232
247
  def parse_finding(self, vuln: Union[Vulnerability, "IntegrationFinding"]) -> Optional["IntegrationFinding"]:
233
248
  """
234
249
  Parses a vulnerability object into an IntegrationFinding object
@@ -240,6 +255,8 @@ class FlatFileImporter(ABC):
240
255
  from regscale.integrations.scanner_integration import IntegrationFinding
241
256
 
242
257
  if isinstance(vuln, IntegrationFinding):
258
+ # We will store this in the properties subsystem of issue.
259
+ vuln.extra_data["source_file_path"] = self.attributes.file_path
243
260
  return vuln
244
261
 
245
262
  try:
@@ -543,21 +560,6 @@ class FlatFileImporter(ABC):
543
560
  if asset not in self.data["assets"]:
544
561
  self.data["assets"].append(asset)
545
562
 
546
- def _check_vuln(self, vuln_to_check: Union[Vulnerability, "IntegrationFinding"]) -> None:
547
- """
548
- Check if the vuln is in the data
549
-
550
- :param Union[Vulnerability, IntegrationFinding] vuln_to_check: The vulnerability to check to prevent duplicates
551
- :rtype: None
552
- """
553
- from regscale.integrations.scanner_integration import IntegrationFinding
554
-
555
- if isinstance(vuln_to_check, IntegrationFinding):
556
- if vuln_to_check not in self.data["vulns"]:
557
- self.data["vulns"].append(vuln_to_check)
558
- elif (vuln_to_check and vuln_to_check not in self.data["vulns"]) and hasattr(vuln_to_check, "id"):
559
- self.data["vulns"].append(vuln_to_check)
560
-
561
563
  def create_vulns(self, func: Callable) -> None:
562
564
  """
563
565
  Create vulns in RegScale from csv file
@@ -585,11 +587,12 @@ class FlatFileImporter(ABC):
585
587
  if not vuln:
586
588
  vuln_progress.advance(vuln_task, advance=1)
587
589
  continue
588
- if isinstance(vuln, Vulnerability) or isinstance(vuln, IntegrationFinding):
589
- self._check_vuln(vuln)
590
+
591
+ if isinstance(vuln, IntegrationFinding):
592
+ self.data["vulns"].append(vuln)
590
593
  if isinstance(vuln, list):
591
594
  for v in vuln:
592
- self._check_vuln(v)
595
+ self.data["vulns"].append(v)
593
596
  if isinstance(vuln, Iterator):
594
597
  self.integration_findings = vuln
595
598
  self.data["vulns"] = vuln
@@ -936,10 +939,21 @@ class FlatFileImporter(ABC):
936
939
  :return: The severity
937
940
  :rtype: str
938
941
  """
942
+ mapping = {
943
+ "critical": IssueSeverity.Critical,
944
+ "high": IssueSeverity.High,
945
+ "medium": IssueSeverity.Moderate,
946
+ "moderate": IssueSeverity.Moderate,
947
+ "low": IssueSeverity.Low,
948
+ "informational": IssueSeverity.NotAssigned,
949
+ "none": IssueSeverity.NotAssigned,
950
+ "info": IssueSeverity.NotAssigned,
951
+ "unknown": IssueSeverity.NotAssigned,
952
+ }
939
953
  severity = "info"
940
954
  if s:
941
955
  severity = s.lower()
942
- return severity
956
+ return mapping.get(severity, IssueSeverity.NotAssigned)
943
957
 
944
958
  @staticmethod
945
959
  def map_status_to_issue_status(status: str) -> IssueStatus:
@@ -8,9 +8,10 @@ from urllib.parse import urlparse
8
8
  from regscale.core.app.application import Application
9
9
  from regscale.core.app.logz import create_logger
10
10
  from regscale.core.app.utils.app_utils import epoch_to_datetime, get_current_datetime, is_valid_fqdn
11
- from regscale.models import ImportValidater, Mapping
11
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
12
+ from regscale.models import ImportValidater
12
13
  from regscale.models.integration_models.flat_file_importer import FlatFileImporter
13
- from regscale.models.regscale_models import Asset, Vulnerability
14
+ from regscale.models.regscale_models import Asset, IssueSeverity, IssueStatus
14
15
 
15
16
  ISSUE_TYPE = "Issue Type"
16
17
  VULNERABILITY_TITLE = ISSUE_TYPE
@@ -53,74 +54,55 @@ class AppScan(FlatFileImporter):
53
54
  **kwargs,
54
55
  )
55
56
 
56
- def create_asset(self, dat: Optional[dict] = None) -> Asset:
57
+ def create_asset(self, dat: Optional[dict] = None) -> IntegrationAsset:
57
58
  """
58
59
  Create an asset from a row in the IBM csv file
59
60
 
60
61
  :param Optional[dict] dat: Data row from CSV file, defaults to None
61
- :return: RegScale Asset object
62
- :rtype: Asset
62
+ :return: RegScale IntegrationAsset object
63
+ :rtype: IntegrationAsset
63
64
  """
64
65
  parsed_url = urlparse(self.mapping.get_value(dat, "URL"))
65
66
  hostname: str = f"{parsed_url.scheme}://{parsed_url.netloc}"
66
- return Asset(
67
+ return IntegrationAsset(
67
68
  **{
68
- "id": 0,
69
69
  "name": hostname,
70
- "isPublic": True,
70
+ "identifier": hostname,
71
71
  "status": "Active (On Network)",
72
- "assetCategory": "Software",
73
- "bLatestScan": True,
74
- "bAuthenticatedScan": True,
75
- "scanningTool": self.name,
76
- "assetOwnerId": self.config["userId"],
77
- "assetType": "Other",
72
+ "asset_category": "Software",
73
+ "scanning_tool": self.name,
74
+ "asset_type": "Other",
78
75
  "fqdn": hostname if is_valid_fqdn(hostname) else None,
79
- "systemAdministratorId": self.config["userId"],
80
- "parentId": self.attributes.parent_id,
81
- "parentModule": self.attributes.parent_module,
82
76
  }
83
77
  )
84
78
 
85
- def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
79
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
86
80
  """
87
- Create a vulnerability from a row in the IBM csv file
88
-
81
+ Create an IntegrationFinding from a row in the IBM csv file
89
82
  :param Optional[dict] dat: Data row from CSV file, defaults to None
90
- :return: RegScale Vulnerability object or None
91
- :rtype: Optional[Vulnerability]
83
+ :return: RegScale IntegrationFinding object or None
84
+ :rtype: Optional[IntegrationFinding]
92
85
  """
86
+
93
87
  regscale_vuln = None
94
88
  parsed_url = urlparse(self.mapping.get_value(dat, "URL"))
95
89
  hostname: str = f"{parsed_url.scheme}://{parsed_url.netloc}"
96
90
  description: str = self.mapping.get_value(dat, ISSUE_TYPE)
97
91
  app_scan_severity = self.mapping.get_value(dat, "Severity")
98
92
  severity = self.severity_map.get(app_scan_severity, "Informational")
99
- config = self.attributes.app.config
100
- asset_match = [asset for asset in self.data["assets"] if asset.name == hostname]
101
- asset = asset_match[0] if asset_match else None
102
- if dat and asset_match:
103
- regscale_vuln = Vulnerability(
104
- id=0,
105
- scanId=0, # set later
106
- parentId=asset.id,
107
- parentModule="assets",
108
- ipAddress="0.0.0.0", # No ip address available
109
- lastSeen=get_current_datetime(),
110
- firstSeen=epoch_to_datetime(self.create_epoch),
111
- daysOpen=None,
112
- dns=hostname,
113
- mitigated=None,
114
- severity=severity,
115
- plugInName=description,
116
- cve="",
117
- vprScore=None,
118
- tenantsId=0,
119
- title=description[:255] if description else "No Title",
93
+ if dat:
94
+ return IntegrationFinding(
95
+ title=self.mapping.get_value(dat, self.vuln_title),
120
96
  description=description,
121
- plugInText=description,
122
- createdById=config["userId"],
123
- lastUpdatedById=config["userId"],
124
- dateCreated=get_current_datetime(),
97
+ cve="",
98
+ severity=self.determine_severity(severity),
99
+ asset_identifier=hostname,
100
+ plugin_name=description,
101
+ plugin_text=description[:255],
102
+ control_labels=[],
103
+ category="Hardware",
104
+ status=IssueStatus.Open,
105
+ last_seen=get_current_datetime(),
106
+ first_seen=epoch_to_datetime(self.create_epoch),
125
107
  )
126
108
  return regscale_vuln
@@ -2,43 +2,47 @@
2
2
  Nexpose Scan information
3
3
  """
4
4
 
5
- from pathlib import Path
6
5
  from typing import Optional
7
6
 
8
7
  from regscale.core.app.application import Application
9
8
  from regscale.core.app.logz import create_logger
10
- from regscale.core.app.utils.app_utils import epoch_to_datetime, get_current_datetime, is_valid_fqdn
9
+ from regscale.core.app.utils.app_utils import epoch_to_datetime, is_valid_fqdn
10
+ from regscale.core.utils.date import date_str
11
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
11
12
  from regscale.models import ImportValidater
12
- from regscale.models.app_models.mapping import Mapping
13
13
  from regscale.models.integration_models.flat_file_importer import FlatFileImporter
14
- from regscale.models.regscale_models import Asset, Issue, Vulnerability
14
+ from regscale.models.regscale_models import Asset, IssueSeverity, IssueStatus
15
15
 
16
16
  VULNERABILITY_TITLE = "Vulnerability Title"
17
17
  VULNERABILITY_ID = "Vulnerability ID"
18
18
  CVSS3_SCORE = "CVSSv3 Score"
19
- IP_ADDRESS = "IP Address"
19
+ CVSS2_SCORE = "CVSSv2 Score"
20
+ IP_ADDRESS = "IP_Address"
21
+ FIRST_SEEN = "first_seen"
22
+ SCAN_DATE = "scan_start_time"
23
+ CVE = "CVEs"
20
24
 
21
25
 
22
- class Nexpose(FlatFileImporter):
26
+ class Nexpose(FlatFileImporter): # pylint: disable=too-many-instance-attributes
23
27
  """
24
- Prisma/Nexpose Scan information
28
+ Nexpose Scan information
25
29
  """
26
30
 
27
- def __init__(self, **kwargs):
31
+ def __init__(self, **kwargs): # pylint: disable=R0902
28
32
  self.name = kwargs.get("name")
29
33
  self.vuln_title = VULNERABILITY_TITLE
30
34
  self.vuln_id = VULNERABILITY_ID
31
35
  self.cvss3_score = CVSS3_SCORE
36
+ self.first_seen = FIRST_SEEN
37
+ self.scan_date_field = SCAN_DATE
38
+ self.cve = CVE
32
39
  self.required_headers = [
33
- "IP Address",
34
40
  "Hostname",
35
- "OS",
36
41
  "Vulnerability Title",
37
42
  "Vulnerability ID",
38
43
  "CVSSv2 Score",
39
44
  "CVSSv3 Score",
40
45
  "Description",
41
- "Proof",
42
46
  "Solution",
43
47
  "CVEs",
44
48
  ]
@@ -60,81 +64,165 @@ class Nexpose(FlatFileImporter):
60
64
  **kwargs,
61
65
  )
62
66
 
63
- def create_asset(self, dat: Optional[dict] = None) -> Optional[Asset]:
67
+ def create_asset(self, dat: Optional[dict] = None) -> Optional[IntegrationAsset]:
64
68
  """
65
69
  Create an asset from a row in the Nexpose csv file
66
70
 
67
71
  :param Optional[dict] dat: Data row from CSV file, defaults to None
68
- :return: RegScale Asset object, if it has a hostname
69
- :rtype: Optional[Asset]
72
+ :return: RegScale IntegrationAsset object, if it has a hostname
73
+ :rtype: Optional[IntegrationAsset]
70
74
  """
71
75
  if hostname := self.mapping.get_value(dat, "Hostname"):
72
- return Asset(
76
+ return IntegrationAsset(
73
77
  **{
74
- "id": 0,
75
78
  "name": hostname,
76
- "ipAddress": self.mapping.get_value(dat, IP_ADDRESS),
77
- "isPublic": True,
79
+ "ip_address": self.mapping.get_value(dat, IP_ADDRESS, "0.0.0.0"),
80
+ "identifier": hostname,
81
+ "other_tracking_number": hostname,
78
82
  "status": "Active (On Network)",
79
- "assetCategory": "Hardware",
80
- "bLatestScan": True,
81
- "bAuthenticatedScan": True,
82
- "scanningTool": self.name,
83
- "assetOwnerId": self.config["userId"],
84
- "assetType": "Other",
83
+ "asset_category": "Hardware",
84
+ "asset_type": "Other",
85
+ "scanning_tool": self.name,
85
86
  "fqdn": hostname if is_valid_fqdn(hostname) else None,
86
- "operatingSystem": Asset.find_os(self.mapping.get_value(dat, "OS")),
87
- "systemAdministratorId": self.config["userId"],
88
- "parentId": self.attributes.parent_id,
89
- "parentModule": self.attributes.parent_module,
87
+ "operating_system": Asset.find_os(self.mapping.get_value(dat, "OS")),
90
88
  }
91
89
  )
90
+ return None
92
91
 
93
- def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
92
+ @staticmethod
93
+ def determine_severity_from_cvss_score(cvss_score: float, cvss_version: str = "v3") -> IssueSeverity:
94
94
  """
95
- Create a vulnerability from a row in the Prisma/Nexpose csv file
95
+ Determine CVSS Severity Text from CVSS Base Score
96
+
97
+ :param float cvss_score: CVSS Base Score
98
+ :param str cvss_version: CVSS version ("v2" or "v3"), defaults to "v3"
99
+ :return: CVSS Severity Text
100
+ :rtype: IssueSeverity
101
+ """
102
+ results = IssueSeverity.Low
103
+
104
+ if cvss_version.lower() == "v3":
105
+ # CVSSv3 severity ranges
106
+ if 4.0 <= cvss_score <= 6.9:
107
+ results = IssueSeverity.Moderate
108
+ elif 7.0 <= cvss_score <= 8.9:
109
+ results = IssueSeverity.High
110
+ elif cvss_score > 8.9:
111
+ results = IssueSeverity.Critical
112
+ elif cvss_version.lower() == "v2":
113
+ # CVSSv2 severity ranges
114
+ if 4.0 <= cvss_score <= 6.9:
115
+ results = IssueSeverity.Moderate
116
+ elif 7.0 <= cvss_score <= 10.0:
117
+ results = IssueSeverity.High
118
+ # CVSSv2 doesn't have a "Critical" category, High is the highest
119
+
120
+ return results
121
+
122
+ def severity_from_text(self, text_severity: str) -> Optional[IssueSeverity]:
123
+ """
124
+ Determine severity from text severity
125
+
126
+ :param str text_severity: Text severity
127
+ :return: IssueSeverity or None
128
+ :rtype: Optional[IssueSeverity]
129
+ """
130
+ if text_severity.lower() == "low":
131
+ return IssueSeverity.Low
132
+ if text_severity.lower() in ["medium", "moderate"]:
133
+ return IssueSeverity.Moderate
134
+ if text_severity.lower() == "high":
135
+ return IssueSeverity.High
136
+ if text_severity.lower() in ["critical", "severe"]:
137
+ return IssueSeverity.Critical
138
+ return None
139
+
140
+ def _determine_severity(self, dat: Optional[dict] = None) -> IssueSeverity:
141
+ """
142
+ Determine severity using the priority order: text severity > CVSSv3 > CVSSv2 > default
143
+
144
+ :param Optional[dict] dat: Data row from CSV file, defaults to None
145
+ :return: Determined IssueSeverity
146
+ :rtype: IssueSeverity
147
+ """
148
+ # Default severity
149
+ severity = IssueSeverity.Low
150
+
151
+ # Extract CVSS scores
152
+ cvss3_score = self.mapping.get_value(dat, self.cvss3_score) or 0.0
153
+ cvss2_score = self.mapping.get_value(dat, CVSS2_SCORE) or 0.0
154
+
155
+ # Priority 1: Check for text-based severity (client-specific)
156
+ # This is client specific, need to be able to override the severity source
157
+ text_severity = self.severity_from_text(
158
+ self.mapping.get_value(dat, "adobe_severity")
159
+ ) or self.severity_from_text(self.mapping.get_value(dat, "nexpose_severity"))
160
+
161
+ if text_severity:
162
+ severity = text_severity
163
+ else:
164
+ # Priority 2: Use CVSSv3 score if available and valid
165
+ if cvss3_score and cvss3_score > 0:
166
+ severity = self.determine_severity_from_cvss_score(float(cvss3_score), "v3")
167
+ # Priority 3: Fall back to CVSSv2 score if available and valid
168
+ elif cvss2_score and cvss2_score > 0:
169
+ severity = self.determine_severity_from_cvss_score(float(cvss2_score), "v2")
170
+
171
+ return severity
172
+
173
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
174
+ """
175
+ Create an IntegrationFinding from a row in the Prisma/Nexpose csv file
96
176
 
97
177
  :param Optional[dict] dat: Data row from CSV file, defaults to None
98
- :return: RegScale Vulnerability object or None
99
- :rtype: Optional[Vulnerability]
178
+ :param kwargs: Additional keyword arguments
179
+ :return: RegScale IntegrationFinding object or None
180
+ :rtype: Optional[IntegrationFinding]
100
181
  """
101
- regscale_vuln = None
102
- cvss3_score = self.mapping.get_value(dat, self.cvss3_score)
182
+ regscale_finding = None
183
+
184
+ # Extract basic data
103
185
  hostname: str = self.mapping.get_value(dat, "Hostname")
104
- os: str = self.mapping.get_value(dat, "OS")
105
186
  description: str = self.mapping.get_value(dat, "Description")
106
- severity = Vulnerability.determine_cvss3_severity_text(float(cvss3_score)) if cvss3_score else "low"
107
- config = self.attributes.app.config
108
- asset_match = [asset for asset in self.data["assets"] if asset.name == hostname]
109
- asset = asset_match[0] if asset_match else None
110
- if dat and asset_match:
111
- return Vulnerability(
112
- id=0,
113
- scanId=0, # set later
114
- parentId=asset.id,
115
- parentModule="assets",
116
- ipAddress="0.0.0.0", # No ip address available
117
- lastSeen=get_current_datetime(),
118
- firstSeen=epoch_to_datetime(self.create_epoch),
119
- daysOpen=None,
120
- dns=hostname,
121
- mitigated=None,
122
- operatingSystem=(Asset.find_os(os) if Asset.find_os(os) else None),
123
- severity=severity,
124
- plugInName=self.mapping.get_value(dat, self.vuln_title),
125
- plugInId=self.mapping.get_value(dat, self.vuln_id),
126
- cve=self.mapping.get_value(dat, "CVEs"),
127
- vprScore=None,
128
- tenantsId=0,
129
- title=description[:255],
187
+ cvss3_score = self.mapping.get_value(dat, self.cvss3_score) or 0.0
188
+
189
+ # Determine severity using priority logic
190
+ severity = self._determine_severity(dat)
191
+
192
+ # Find matching asset
193
+
194
+ # Extract date information
195
+ first_seen = (
196
+ self.mapping.get_value(dat, self.first_seen)
197
+ or self.mapping.get_value(dat, "first_seen")
198
+ or epoch_to_datetime(self.create_epoch)
199
+ )
200
+
201
+ if scan_date := self.mapping.get_value(dat, SCAN_DATE):
202
+ self.scan_date = scan_date
203
+ cvss_score = self.mapping.get_value(dat, CVSS2_SCORE)
204
+
205
+ # Create IntegrationFinding if we have valid data and asset match
206
+ if dat:
207
+ return IntegrationFinding(
208
+ control_labels=[], # Add an empty list for control_labels
209
+ title=self.mapping.get_value(dat, self.vuln_title),
130
210
  description=description,
131
- plugInText=self.mapping.get_value(dat, self.vuln_title),
132
- createdById=config["userId"],
133
- lastUpdatedById=config["userId"],
134
- dateCreated=get_current_datetime(),
135
- extra_data={
136
- "solution": self.mapping.get_value(dat, "Solution"),
137
- "proof": self.mapping.get_value(dat, "Proof"),
138
- },
211
+ cve=self.mapping.get_value(dat, self.cve, "").upper(),
212
+ severity=severity,
213
+ asset_identifier=hostname,
214
+ plugin_name=self.mapping.get_value(dat, self.vuln_title),
215
+ plugin_id=str(self.mapping.get_value(dat, self.vuln_id)),
216
+ cvss_score=cvss_score or 0.0,
217
+ cvss_v3_score=cvss3_score or 0.0,
218
+ cvss_v2_score=cvss_score or 0.0,
219
+ plugin_text=description[:255],
220
+ remediation=self.mapping.get_value(dat, "Solution"),
221
+ category="Hardware",
222
+ status=IssueStatus.Open,
223
+ first_seen=self.scan_date or date_str(first_seen),
224
+ scan_date=date_str(scan_date) or self.scan_date,
225
+ vulnerability_type="Vulnerability Scan",
226
+ baseline=f"{self.name} Host",
139
227
  )
140
- return regscale_vuln
228
+ return regscale_finding