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
@@ -10,11 +10,10 @@ from typing import Any, List, Optional
10
10
  from regscale.core.app.application import Application
11
11
  from regscale.core.app.logz import create_logger
12
12
  from regscale.core.app.utils.app_utils import epoch_to_datetime, get_current_datetime, is_valid_fqdn
13
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
13
14
  from regscale.models import ImportValidater
14
15
  from regscale.models.integration_models.flat_file_importer import FlatFileImporter
15
- from regscale.models.regscale_models import SoftwareInventory
16
- from regscale.models.regscale_models.asset import Asset
17
- from regscale.models.regscale_models.vulnerability import Vulnerability
16
+ from regscale.models.regscale_models import Asset, IssueSeverity, IssueStatus, SoftwareInventory, Vulnerability
18
17
 
19
18
  FIX_STATUS = "Fix Status"
20
19
  VULNERABILITY_ID = "Vulnerability ID"
@@ -38,7 +37,7 @@ class Prisma(FlatFileImporter):
38
37
 
39
38
  def __init__(self, **kwargs):
40
39
  self.name = kwargs.get("name")
41
- regscale_ssp_id = kwargs.get("regscale_ssp_id")
40
+ regscale_ssp_id = kwargs.get("object_id")
42
41
  self.image_name = "Id"
43
42
  self.vuln_title = CVE_ID
44
43
  self.cvss3_score = "CVSS"
@@ -63,94 +62,75 @@ class Prisma(FlatFileImporter):
63
62
  ignore_validation=True,
64
63
  **kwargs,
65
64
  )
66
- self.create_software_inventory()
65
+ # self.create_software_inventory()
67
66
 
68
- def create_asset(self, dat: Optional[dict] = None) -> Asset:
67
+ def create_asset(self, dat: Optional[dict] = None) -> IntegrationAsset:
69
68
  """
70
69
  Create an asset from a row in the Prisma csv file
71
70
 
72
71
  :param Optional[dict] dat: Data row from CSV file, defaults to None
73
- :return: RegScale Asset object
74
- :rtype: Asset
72
+ :return: RegScale IntegrationAsset object
73
+ :rtype: IntegrationAsset
75
74
  """
76
75
  hostname = self.mapping.get_value(dat, "Hostname")
77
76
  distro = self.mapping.get_value(dat, "Distro")
78
77
 
79
- return Asset(
78
+ return IntegrationAsset(
80
79
  **{
81
- "id": 0,
82
80
  "name": hostname,
83
- "ipAddress": self.mapping.get_value(dat, "IP Address"),
84
- "isPublic": True,
81
+ "ip_address": self.mapping.get_value(dat, "IP Address"),
82
+ "identifier": hostname,
83
+ "other_tracking_number": hostname,
85
84
  "status": "Active (On Network)",
86
- "assetCategory": "Hardware",
87
- "bLatestScan": True,
88
- "bAuthenticatedScan": True,
89
- "scanningTool": self.name,
90
- "assetOwnerId": self.config["userId"],
91
- "assetType": "Other",
85
+ "asset_category": "Hardware",
86
+ "asset_type": "Other",
87
+ "scanning_tool": self.name,
92
88
  "fqdn": hostname if is_valid_fqdn(hostname) else None,
93
- "operatingSystem": Asset.find_os(distro),
94
- "systemAdministratorId": self.config["userId"],
95
- "parentId": self.attributes.parent_id,
96
- "parentModule": self.attributes.parent_module,
89
+ "operating_system": Asset.find_os(distro),
97
90
  }
98
91
  )
99
92
 
100
- def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
93
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
101
94
  """
102
- Create a vulnerability from a row in the Prisma csv file
95
+ Create a IntegrationFinding from a row in the Prisma csv file
103
96
 
104
97
  :param Optional[dict] dat: Data row from CSV file, defaults to None
105
- :return: RegScale Vulnerability object or None
106
- :rtype: Optional[Vulnerability]
98
+ :return: RegScale IntegrationFinding object or None
99
+ :rtype: Optional[IntegrationFinding]
107
100
  """
108
101
  cvss3_score = self.mapping.get_value(dat, self.cvss3_score)
109
102
  hostname: str = self.mapping.get_value(dat, "Hostname")
110
- distro: str = self.mapping.get_value(dat, "Distro")
111
103
  cve: str = self.mapping.get_value(dat, CVE_ID)
112
104
  description: str = self.mapping.get_value(dat, "Description")
113
- title = self.mapping.get_value(dat, self.vuln_title)
114
- regscale_vuln = None
115
- severity = Vulnerability.determine_cvss3_severity_text(float(cvss3_score)) if cvss3_score else "low"
116
- config = self.attributes.app.config
117
- asset_match = [asset for asset in self.data["assets"] if asset.name == hostname]
118
- asset = asset_match[0] if asset_match else None
119
- if dat and asset_match:
120
- return Vulnerability(
121
- id=0,
122
- scanId=0, # set later
123
- parentId=asset.id,
124
- parentModule="assets",
125
- ipAddress="0.0.0.0", # No ip address available
126
- lastSeen=epoch_to_datetime(self.create_epoch),
127
- firstSeen=epoch_to_datetime(self.create_epoch),
128
- daysOpen=None,
129
- dns=hostname,
130
- mitigated=None,
131
- operatingSystem=(Asset.find_os(distro) if Asset.find_os(distro) else None),
105
+ regscale_finding: Optional[IntegrationFinding] = None
106
+ severity = (
107
+ self.determine_severity(Vulnerability.determine_cvss3_severity_text(float(cvss3_score)))
108
+ if cvss3_score
109
+ else IssueSeverity.NotAssigned
110
+ )
111
+ seen = epoch_to_datetime(self.create_epoch)
112
+ if dat:
113
+ return IntegrationFinding(
114
+ control_labels=[], # Add an empty list for control_labels
115
+ title=self.mapping.get_value(dat, self.vuln_title),
116
+ description=description,
117
+ cve=cve.upper(),
132
118
  severity=severity,
133
- plugInName=self.mapping.get_value(dat, self.vuln_title),
134
- plugInId=(
135
- self.mapping.get_value(dat, VULNERABILITY_ID)
136
- if self.mapping.get_value(dat, VULNERABILITY_ID)
137
- else None
138
- ),
139
- cve=cve,
140
- vprScore=None,
141
- tenantsId=0,
142
- title=title,
143
- description=description[:255],
144
- plugInText=title,
145
- createdById=config["userId"],
146
- lastUpdatedById=config["userId"],
147
- dateCreated=get_current_datetime(),
148
- extra_data={
149
- "solution": self.mapping.get_value(dat, FIX_STATUS),
150
- "proof": self.mapping.get_value(dat, FIX_STATUS),
151
- },
119
+ asset_identifier=hostname,
120
+ plugin_name=self.mapping.get_value(dat, self.vuln_title),
121
+ plugin_id=self.mapping.get_value(dat, VULNERABILITY_ID),
122
+ cvss_v3_score=cvss3_score or 0.0,
123
+ plugin_text=description[:255],
124
+ remediation=self.mapping.get_value(dat, "Solution"),
125
+ category="Hardware",
126
+ status=IssueStatus.Open,
127
+ first_seen=seen,
128
+ scan_date=seen,
129
+ vulnerability_type="Vulnerability Scan",
130
+ baseline=f"{self.name} Host",
131
+ recommendation_for_mitigation=self.mapping.get_value(dat, FIX_STATUS),
152
132
  )
153
- return regscale_vuln
133
+ return regscale_finding
154
134
 
155
135
  def create_software_inventory(self) -> List[SoftwareInventory]:
156
136
  """
@@ -9,7 +9,7 @@ import logging
9
9
  import re
10
10
  from calendar import firstweekday
11
11
  from datetime import datetime
12
- from typing import Any, Iterator, Optional, TypeVar, TextIO, Union
12
+ from typing import Any, Iterator, List, Optional, TextIO, TypeVar, Union
13
13
 
14
14
  from openpyxl.reader.excel import load_workbook
15
15
  from pandas import Timestamp
@@ -17,20 +17,21 @@ from pandas import Timestamp
17
17
  from regscale.core.app import create_logger
18
18
  from regscale.core.app.application import Application
19
19
  from regscale.core.app.utils.app_utils import get_current_datetime
20
- from regscale.core.app.utils.parser_utils import safe_int, safe_float
20
+ from regscale.core.app.utils.parser_utils import safe_float, safe_int
21
21
  from regscale.core.utils.date import date_str, datetime_str
22
- from regscale.integrations.scanner_integration import IntegrationAsset
22
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
23
23
  from regscale.models import (
24
24
  Asset,
25
25
  AssetCategory,
26
+ AssetStatus,
26
27
  AssetType,
27
28
  ImportValidater,
28
29
  IssueSeverity,
30
+ IssueStatus,
29
31
  SecurityPlan,
30
32
  Vulnerability,
31
- VulnerabilityStatus,
32
- AssetStatus,
33
33
  VulnerabilitySeverity,
34
+ VulnerabilityStatus,
34
35
  )
35
36
  from regscale.models.integration_models.flat_file_importer import FlatFileImporter
36
37
 
@@ -50,6 +51,18 @@ FQDN = "FQDN"
50
51
  IMAGE_ID_FIELD = "IMAGE ID"
51
52
  CVE_ID_FIELD = "CVE ID"
52
53
 
54
+ SEVERITY_MAP = {
55
+ "critical": IssueSeverity.Critical,
56
+ "high": IssueSeverity.High,
57
+ "medium": IssueSeverity.Moderate,
58
+ "moderate": IssueSeverity.Moderate,
59
+ "low": IssueSeverity.Low,
60
+ "informational": IssueSeverity.NotAssigned,
61
+ "none": IssueSeverity.NotAssigned,
62
+ "info": IssueSeverity.NotAssigned,
63
+ "unknown": IssueSeverity.NotAssigned,
64
+ }
65
+
53
66
 
54
67
  class Qualys(FlatFileImporter):
55
68
  """Qualys Scan information"""
@@ -94,36 +107,30 @@ class Qualys(FlatFileImporter):
94
107
  # header is line# 11
95
108
  # start self.file_data from line #12
96
109
 
97
- def create_asset(self, dat: Optional[dict] = None) -> Optional[Asset]:
110
+ def create_asset(self, dat: Optional[dict] = None) -> Optional[IntegrationAsset]:
98
111
  """
99
- Create an asset from a row in the Qualys file
112
+ Create an integration asset from a row in the Qualys file
100
113
 
101
114
  :param Optional[dict] dat: Data row from CSV file
102
- :return: RegScale Issue object or None
103
- :rtype: Optional[Asset]
115
+ :return: RegScale IntegrationAsset object or None
116
+ :rtype: Optional[IntegrationAsset]
104
117
  """
105
- return Asset(
106
- **{
107
- "id": 0,
108
- "name": self.mapping.get_value(dat, DNS),
109
- "ipAddress": self.mapping.get_value(dat, IP),
110
- "isPublic": True,
111
- "status": "Active (On Network)",
112
- "assetCategory": "Hardware",
113
- "qualysId": str(self.mapping.get_value(dat, QG_HOST_ID)), # UUID from Nessus HostProperties tag
114
- "bLatestScan": True,
115
- "bAuthenticatedScan": True,
116
- "scanningTool": "Qualys",
117
- "assetOwnerId": self.attributes.app.config["userId"],
118
- "netBIOS": self.mapping.get_value(dat, NETBIOS),
119
- "assetType": "Other",
120
- "fqdn": self.mapping.get_value(dat, FQDN),
121
- "operatingSystem": Asset.find_os(self.mapping.get_value(dat, OS)),
122
- "operatingSystemVersion": self.mapping.get_value(dat, OS),
123
- "systemAdministratorId": self.attributes.app.config["userId"],
124
- "parentId": self.attributes.parent_id,
125
- "parentModule": self.attributes.parent_module,
126
- }
118
+ qid = str(self.mapping.get_value(dat, QG_HOST_ID))
119
+ return IntegrationAsset(
120
+ name=self.mapping.get_value(dat, DNS),
121
+ ip_address=self.mapping.get_value(dat, IP),
122
+ status=AssetStatus.Active.value,
123
+ cpu=0,
124
+ ram=0,
125
+ asset_category="Hardware",
126
+ identifier=qid, # UUID from Qualys Host ID
127
+ other_tracking_number=qid,
128
+ scanning_tool="Qualys",
129
+ asset_owner_id=self.attributes.app.config["userId"],
130
+ asset_type="Other",
131
+ fqdn=self.mapping.get_value(dat, FQDN),
132
+ operating_system=Asset.find_os(self.mapping.get_value(dat, OS)),
133
+ os_version=self.mapping.get_value(dat, OS),
127
134
  )
128
135
 
129
136
  def _convert_datetime_to_str(self, input_date: Union[Timestamp, datetime]) -> str:
@@ -140,59 +147,43 @@ class Qualys(FlatFileImporter):
140
147
  return input_date
141
148
  return input_date.strftime(self.dt_format)
142
149
 
143
- def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
150
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
144
151
  """
145
- Create a vuln from a row in the Qualys file
152
+ Create a finding from a row in the Qualys file
146
153
 
147
154
  :param Optional[dict] dat: Data row from CSV file, defaults to None
148
- :return: RegScale Vulnerability object or None
149
- :rtype: Optional[Vulnerability]
155
+ :rtype: IntegrationFinding
150
156
  """
151
157
  from regscale.integrations.commercial.qualys import map_qualys_severity_to_regscale
152
158
 
153
- dns: str = self.mapping.get_value(dat, DNS)
154
- other_id: str = self.mapping.get_value(dat, QG_HOST_ID)
155
- distro: str = self.mapping.get_value(dat, OS)
159
+ finding: Optional[IntegrationFinding] = None
160
+ qid = str(self.mapping.get_value(dat, QG_HOST_ID))
156
161
  cve: str = self.mapping.get_value(dat, CVE_ID)
157
162
  description: str = self.mapping.get_value(dat, "Threat")
158
163
  title = self.mapping.get_value(dat, self.vuln_title)
159
- regscale_vuln = None
160
164
  severity = self.mapping.get_value(dat, SEVERITY)
161
165
  regscale_severity = map_qualys_severity_to_regscale(int(severity))[1]
162
- config = self.attributes.app.config
163
- asset_match = [asset for asset in self.data["assets"] if asset.name == dns]
164
- asset = asset_match[0] if asset_match else None
165
- last_seen = self._convert_datetime_to_str(self.mapping.get_value(dat, "Last Detected"))
166
- first_seen = self._convert_datetime_to_str(self.mapping.get_value(dat, "First Detected"))
167
-
168
- if dat and asset_match:
169
- regscale_vuln = Vulnerability(
170
- id=0,
171
- scanId=0, # set later
172
- parentId=asset.id,
173
- parentModule="assets",
174
- ipAddress=self.mapping.get_value(dat, IP),
175
- lastSeen=last_seen,
176
- firstSeen=first_seen,
177
- daysOpen=None,
178
- dns=self.mapping.get_value(dat, DNS, other_id),
179
- mitigated=None,
180
- operatingSystem=(Asset.find_os(distro) if Asset.find_os(distro) else None),
181
- severity=regscale_severity,
182
- plugInName=self.mapping.get_value(dat, self.vuln_title),
183
- plugInId=self.mapping.get_value(dat, "QID"),
184
- cve=cve,
185
- vprScore=None,
186
- cvsSv3BaseScore=self.extract_float(self.mapping.get_value(dat, "CVSS3.1 Base", 0.0)),
187
- tenantsId=0,
166
+ if dat:
167
+ finding = IntegrationFinding(
168
+ control_labels=[], # Add an empty list for control_labels
188
169
  title=title,
189
170
  description=description,
190
- plugInText=title,
191
- createdById=config["userId"],
192
- lastUpdatedById=config["userId"],
193
- dateCreated=get_current_datetime(),
171
+ ip_address="0.0.0.0",
172
+ cve=cve,
173
+ severity=regscale_severity,
174
+ asset_identifier=qid,
175
+ plugin_name=description,
176
+ plugin_id=str(self.mapping.get_value(dat, "QID")),
177
+ cvss_v3_score=self.extract_float(self.mapping.get_value(dat, "CVSS3.1 Base", 0.0)),
178
+ plugin_text=title,
179
+ category="Hardware",
180
+ status=IssueStatus.Open,
181
+ first_seen=self._convert_datetime_to_str(self.mapping.get_value(dat, "First Detected")),
182
+ last_seen=self._convert_datetime_to_str(self.mapping.get_value(dat, "Last Detected")),
183
+ vulnerability_type="Vulnerability Scan",
184
+ baseline=f"{self.name} Host",
194
185
  )
195
- return regscale_vuln
186
+ return finding
196
187
 
197
188
  @staticmethod
198
189
  def extract_float(s: Union[str, float, int]) -> Optional[float]:
@@ -230,6 +221,7 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
230
221
  last_seen_field = "UPDATED"
231
222
  container_id = "IMAGE UUID"
232
223
  records = []
224
+ title = "Qualys Container Scanner Export Integration" # Add explicit title for source report identification
233
225
 
234
226
  def __init__(self, **kwargs: dict):
235
227
  self.asset_identifier_field = "otherTrackingNumber"
@@ -278,26 +270,25 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
278
270
  **kwargs,
279
271
  )
280
272
 
281
- def create_asset(self, row: Optional[dict] = None, **kwargs) -> Optional[Asset]:
273
+ def create_asset(self, row: Optional[dict] = None, **kwargs) -> Optional[IntegrationAsset]:
282
274
  """
283
275
  Fetch assets from the Qualys CSV file
284
276
 
285
- :return: Iterator of IntegrationAsset objects
286
- :rtype: Iterator[IntegrationAsset]
277
+ :return: IntegrationAsset object
278
+ :rtype: Optional[IntegrationAsset]
287
279
  """
288
280
  bad_ids = ["", "0", "None", "Unknown"]
289
281
  if self.mapping.get_value(row, self.container_id) in bad_ids:
290
282
  return None
291
283
  max_length = 450 # max length of asset name
292
- asset = Asset(
284
+ container_id = self.mapping.get_value(row, self.container_id)
285
+ asset = IntegrationAsset(
293
286
  name=self.mapping.get_value(row, self.asset_notes_field)[:max_length] or "Unknown",
294
287
  notes=self.mapping.get_value(row, self.asset_notes_field),
295
- otherTrackingNumber=self.mapping.get_value(row, self.container_id),
296
- # identifier=self.mapping.get_value(row, IMAGE_ID_FIELD),
297
- assetType=AssetType.VM.value,
298
- assetCategory=AssetCategory.Software.value,
299
- parentId=safe_int(self.plan_id),
300
- parentModule=SecurityPlan.get_module_slug(),
288
+ identifier=container_id,
289
+ other_tracking_number=container_id,
290
+ asset_type=AssetType.VM.value,
291
+ asset_category=AssetCategory.Software.value,
301
292
  status=AssetStatus.Active,
302
293
  )
303
294
  return asset
@@ -316,34 +307,44 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
316
307
  )
317
308
  return date_str(date_obj_value, self.dt_format)
318
309
 
319
- def create_vuln(self, row: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
310
+ def create_vuln(self, row: Optional[dict] = None, **kwargs) -> Iterator[IntegrationFinding]:
320
311
  """
321
312
  Fetch vulnerabilities from the Qualys CSV file
322
313
 
323
314
  :return: Iterator of IntegrationFinding objects
324
- :rtype: Vulnerability
315
+ :rtype: Iterator[IntegrationFinding]
325
316
  """
326
-
317
+ findings: List[IntegrationFinding] = []
318
+ title = self.mapping.get_value(row, self.title_field)
319
+ description = self.mapping.get_value(row, self.threat_field)
320
+ qid = str(self.mapping.get_value(row, self.container_id))
327
321
  if self.mapping.get_value(row, CVE_ID_FIELD) and isinstance(self.mapping.get_value(row, CVE_ID_FIELD), str):
328
322
  for cve_id in self.mapping.get_value(row, CVE_ID_FIELD, "").split(","):
329
- finding = Vulnerability(
323
+ finding = IntegrationFinding(
324
+ control_labels=[], # Add an empty list for control_labels
330
325
  title=self.mapping.get_value(row, self.title_field),
331
326
  description=self.mapping.get_value(row, self.threat_field),
332
327
  cve=cve_id,
333
328
  severity=severity_to_regscale(self.mapping.get_value(row, self.severity_field)),
334
- status=VulnerabilityStatus.Open,
335
- cvsSv3BaseScore=safe_float(self.mapping.get_value(row, self.cve_v3_base, 0.0)),
336
- vprScore=safe_float(self.mapping.get_value(row, self.cve_base, 0.0)),
337
- firstSeen=self.handle_integration_date(
329
+ asset_identifier=qid,
330
+ plugin_name=description,
331
+ plugin_id=str(self.mapping.get_value(dat, "QID")),
332
+ cvss_v3_score=safe_float(self.mapping.get_value(row, self.cve_v3_base, 0.0)),
333
+ vpr_score=safe_float(self.mapping.get_value(row, self.cve_base, 0.0)),
334
+ plugin_text=title,
335
+ category="Hardware",
336
+ status=IssueStatus.Open,
337
+ first_seen=self.handle_integration_date(
338
338
  self.mapping.get_value(row, self.first_seen_field, get_current_datetime())
339
339
  ),
340
- lastSeen=self.handle_integration_date(
340
+ last_seen=self.handle_integration_date(
341
341
  self.mapping.get_value(row, self.last_seen_field, get_current_datetime())
342
342
  ),
343
- plugInName=cve_id,
344
- dns=self.mapping.get_value(row, self.container_id), # really asset_identifier
343
+ vulnerability_type="Vulnerability Scan",
344
+ baseline=f"{self.name} Host",
345
345
  )
346
- return finding
346
+ findings.append(finding)
347
+ yield from findings
347
348
 
348
349
 
349
350
  class QualysWasScansImporter(FlatFileImporter):
@@ -365,6 +366,7 @@ class QualysWasScansImporter(FlatFileImporter):
365
366
  container_id = "ID"
366
367
  row_key = "VULNERABILITY"
367
368
  records = []
369
+ title = "Qualys WAS Scanner Export Integration" # Add explicit title for source report identification
368
370
 
369
371
  def __init__(self, **kwargs: dict):
370
372
  self.asset_identifier_field = "otherTrackingNumber"
@@ -561,6 +563,8 @@ class QualysWasScansImporter(FlatFileImporter):
561
563
  :rtype: Vulnerability
562
564
  """
563
565
  # additional_fields = ["Title", "Severity Level", "CVSS Base", "CWE", "Solution"]
566
+ asset_identifier = self.mapping.get_value(row, self.container_id)
567
+
564
568
  finding = Vulnerability(
565
569
  title=row.get("Title", self.mapping.get_value(row, "Url")),
566
570
  description=row.get("Solution"),
@@ -576,7 +580,8 @@ class QualysWasScansImporter(FlatFileImporter):
576
580
  self.mapping.get_value(row, self.last_seen_field, get_current_datetime())
577
581
  ),
578
582
  plugInName=row.get("CWE"),
579
- dns=self.mapping.get_value(row, self.container_id), # really asset_identifier
583
+ dns=asset_identifier, # Use consistent asset identifier
584
+ assetIdentifier=asset_identifier, # Add explicit asset identifier
580
585
  )
581
586
  return finding
582
587
 
@@ -588,6 +593,7 @@ class QualysPolicyScansImporter(FlatFileImporter):
588
593
 
589
594
  plan_id = 0
590
595
  records = []
596
+ title = "Qualys Policy Scanner Export Integration" # Add explicit title for source report identification
591
597
 
592
598
  def __init__(self, **kwargs: dict):
593
599
  self.asset_identifier_field = "qualysId"