regscale-cli 6.20.7.0__py3-none-any.whl → 6.20.9.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 (41) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/api.py +8 -1
  3. regscale/core/app/application.py +130 -20
  4. regscale/core/utils/date.py +16 -16
  5. regscale/integrations/commercial/aqua/aqua.py +1 -1
  6. regscale/integrations/commercial/aws/cli.py +1 -1
  7. regscale/integrations/commercial/defender.py +1 -1
  8. regscale/integrations/commercial/ecr.py +1 -1
  9. regscale/integrations/commercial/ibm.py +1 -1
  10. regscale/integrations/commercial/nexpose.py +1 -1
  11. regscale/integrations/commercial/prisma.py +1 -1
  12. regscale/integrations/commercial/qualys/__init__.py +150 -77
  13. regscale/integrations/commercial/qualys/containers.py +2 -1
  14. regscale/integrations/commercial/qualys/scanner.py +5 -3
  15. regscale/integrations/commercial/snyk.py +14 -4
  16. regscale/integrations/commercial/synqly/ticketing.py +23 -11
  17. regscale/integrations/commercial/veracode.py +15 -4
  18. regscale/integrations/commercial/xray.py +1 -1
  19. regscale/integrations/public/cisa.py +7 -1
  20. regscale/integrations/public/nist_catalog.py +8 -2
  21. regscale/integrations/scanner_integration.py +18 -36
  22. regscale/models/integration_models/cisa_kev_data.json +51 -6
  23. regscale/models/integration_models/flat_file_importer/__init__.py +34 -19
  24. regscale/models/integration_models/send_reminders.py +8 -2
  25. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  26. regscale/models/regscale_models/control_implementation.py +40 -0
  27. regscale/models/regscale_models/issue.py +7 -4
  28. regscale/models/regscale_models/parameter.py +3 -2
  29. regscale/models/regscale_models/ports_protocol.py +15 -5
  30. regscale/models/regscale_models/vulnerability.py +1 -1
  31. regscale/utils/graphql_client.py +3 -6
  32. regscale/utils/threading/threadhandler.py +12 -2
  33. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/METADATA +13 -13
  34. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/RECORD +41 -40
  35. tests/regscale/core/test_app.py +402 -16
  36. tests/regscale/core/test_version_regscale.py +62 -0
  37. tests/regscale/test_init.py +2 -0
  38. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/LICENSE +0 -0
  39. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/WHEEL +0 -0
  40. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/entry_points.txt +0 -0
  41. {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.9.0.dist-info}/top_level.txt +0 -0
@@ -52,13 +52,6 @@ def sync_freshdesk(regscale_id: int, regscale_module: str, name: str, subject: s
52
52
  @ticketing.command(name="sync_jira")
53
53
  @regscale_id()
54
54
  @regscale_module()
55
- @click.option(
56
- "--issue_type",
57
- type=click.STRING,
58
- help="jira issue type",
59
- required=True,
60
- prompt="jira issue type",
61
- )
62
55
  @click.option(
63
56
  "--project",
64
57
  type=click.STRING,
@@ -66,6 +59,12 @@ def sync_freshdesk(regscale_id: int, regscale_module: str, name: str, subject: s
66
59
  required=True,
67
60
  prompt="jira project",
68
61
  )
62
+ @click.option(
63
+ "--default_issue_type",
64
+ type=click.STRING,
65
+ help="Default Issue Type for the integration. If provided, the issue_type field becomes optional in ticket creation requests.",
66
+ required=False,
67
+ )
69
68
  @click.option(
70
69
  "--default_project",
71
70
  type=click.STRING,
@@ -80,7 +79,12 @@ def sync_freshdesk(regscale_id: int, regscale_module: str, name: str, subject: s
80
79
  default=True,
81
80
  )
82
81
  def sync_jira(
83
- regscale_id: int, regscale_module: str, issue_type: str, project: str, default_project: str, sync_attachments: bool
82
+ regscale_id: int,
83
+ regscale_module: str,
84
+ project: str,
85
+ default_issue_type: str,
86
+ default_project: str,
87
+ sync_attachments: bool,
84
88
  ) -> None:
85
89
  """Sync Ticketing data between Jira and RegScale."""
86
90
  from regscale.models.integration_models.synqly_models.connectors import Ticketing
@@ -89,8 +93,8 @@ def sync_jira(
89
93
  ticketing_jira.run_sync(
90
94
  regscale_id=regscale_id,
91
95
  regscale_module=regscale_module,
92
- issue_type=issue_type,
93
96
  project=project,
97
+ default_issue_type=default_issue_type,
94
98
  default_project=default_project,
95
99
  sync_attachments=sync_attachments,
96
100
  )
@@ -157,12 +161,20 @@ def sync_servicenow(regscale_id: int, regscale_module: str, issue_type: str, def
157
161
  required=True,
158
162
  prompt="servicenow_sir issue type",
159
163
  )
160
- def sync_servicenow_sir(regscale_id: int, regscale_module: str, issue_type: str) -> None:
164
+ @click.option(
165
+ "--default_project",
166
+ type=click.STRING,
167
+ help="Default Project for the integration. This maps to the custom table for tickets. This table should be derived from Security Incident table. Defaults to the security incident table if not specified.",
168
+ required=False,
169
+ )
170
+ def sync_servicenow_sir(regscale_id: int, regscale_module: str, issue_type: str, default_project: str) -> None:
161
171
  """Sync Ticketing data between Servicenow Sir and RegScale."""
162
172
  from regscale.models.integration_models.synqly_models.connectors import Ticketing
163
173
 
164
174
  ticketing_servicenow_sir = Ticketing("servicenow_sir")
165
- ticketing_servicenow_sir.run_sync(regscale_id=regscale_id, regscale_module=regscale_module, issue_type=issue_type)
175
+ ticketing_servicenow_sir.run_sync(
176
+ regscale_id=regscale_id, regscale_module=regscale_module, issue_type=issue_type, default_project=default_project
177
+ )
166
178
 
167
179
 
168
180
  @ticketing.command(name="sync_torq")
@@ -28,10 +28,12 @@ FlatFileImporter.show_mapping(
28
28
  message="File path to the folder containing Veracode .xlsx files to process to RegScale.",
29
29
  prompt="File path for Veracode files",
30
30
  import_name="veracode",
31
+ support_component=True,
31
32
  )
32
33
  def import_veracode(
33
34
  folder_path: PathLike[str],
34
35
  regscale_ssp_id: int,
36
+ component_id: int,
35
37
  scan_date: datetime,
36
38
  mappings_path: Path,
37
39
  disable_mapping: bool,
@@ -43,9 +45,15 @@ def import_veracode(
43
45
  """
44
46
  Import scans, vulnerabilities and assets to RegScale from Veracode export files
45
47
  """
48
+ if not regscale_ssp_id and not component_id:
49
+ raise click.UsageError(
50
+ "You must provide either a --regscale_ssp_id or a --component_id to import Veracode scans."
51
+ )
52
+
46
53
  import_veracode_data(
47
54
  folder_path=folder_path,
48
- regscale_ssp_id=regscale_ssp_id,
55
+ object_id=component_id if component_id else regscale_ssp_id,
56
+ is_component=bool(component_id),
49
57
  scan_date=scan_date,
50
58
  mappings_path=mappings_path,
51
59
  disable_mapping=disable_mapping,
@@ -58,7 +66,7 @@ def import_veracode(
58
66
 
59
67
  def import_veracode_data(
60
68
  folder_path: PathLike[str],
61
- regscale_ssp_id: int,
69
+ object_id: int,
62
70
  scan_date: datetime,
63
71
  mappings_path: Path,
64
72
  s3_bucket: str,
@@ -66,11 +74,13 @@ def import_veracode_data(
66
74
  aws_profile: str,
67
75
  disable_mapping: Optional[bool] = False,
68
76
  upload_file: Optional[bool] = True,
77
+ is_component: Optional[bool] = False,
69
78
  ) -> None:
70
79
  """Import scans, vulnerabilities and assets to RegScale from Veracode export files"
71
80
 
72
81
  :param os.PathLike[str] folder_path: Path to the folder containing Veracode files
73
- :param int regscale_ssp_id: RegScale SSP ID
82
+ :param int object_id: RegScale SSP ID or Component ID
83
+ :param bool is_component: Whether object_id is a component or not
74
84
  :param datetime scan_date: Scan date
75
85
  :param os.PathLike[str] mappings_path: Path to the header mapping file
76
86
  :param str s3_bucket: S3 bucket to download the files from
@@ -85,7 +95,7 @@ def import_veracode_data(
85
95
  import_name="Veracode",
86
96
  file_types=[".xml", ".xlsx", ".json"],
87
97
  folder_path=folder_path,
88
- regscale_ssp_id=regscale_ssp_id,
98
+ object_id=object_id,
89
99
  scan_date=scan_date,
90
100
  mappings_path=mappings_path,
91
101
  disable_mapping=disable_mapping,
@@ -93,4 +103,5 @@ def import_veracode_data(
93
103
  s3_prefix=s3_prefix,
94
104
  aws_profile=aws_profile,
95
105
  upload_file=upload_file,
106
+ is_component=is_component,
96
107
  )
@@ -80,7 +80,7 @@ def import_xray_files(
80
80
  import_name="XRay",
81
81
  file_types=".json",
82
82
  folder_path=folder_path,
83
- regscale_ssp_id=regscale_ssp_id,
83
+ object_id=regscale_ssp_id,
84
84
  scan_date=scan_date,
85
85
  mappings_path=mappings_path,
86
86
  disable_mapping=disable_mapping,
@@ -171,7 +171,13 @@ def parse_html(page_url: str, app: Application) -> list:
171
171
  control["items"] = len(articles)
172
172
  control["page"] += 1
173
173
  # check if max threads <= 20 to prevent IP ban from CISA
174
- max_threads = min(app.config["maxThreads"], 20)
174
+ max_threads_config = app.config.get("maxThreads", 100)
175
+ if not isinstance(max_threads_config, int):
176
+ try:
177
+ max_threads_config = int(max_threads_config)
178
+ except (ValueError, TypeError):
179
+ max_threads_config = 100
180
+ max_threads = min(max_threads_config, 20)
175
181
  with ThreadPoolExecutor(max_workers=max_threads) as executor:
176
182
  futures = []
177
183
  for link in control["links"]:
@@ -67,8 +67,14 @@ def sort_controls_by_id(catalog_id: int) -> None:
67
67
  api = Api()
68
68
  config = app.config
69
69
  # update api limits depending on maxThreads
70
- api.pool_connections = max(api.pool_connections, config["maxThreads"])
71
- api.pool_maxsize = max(api.pool_maxsize, config["maxThreads"])
70
+ max_threads = config.get("maxThreads", 100)
71
+ if not isinstance(max_threads, int):
72
+ try:
73
+ max_threads = int(max_threads)
74
+ except (ValueError, TypeError):
75
+ max_threads = 100
76
+ api.pool_connections = max(api.pool_connections, max_threads)
77
+ api.pool_maxsize = max(api.pool_maxsize, max_threads)
72
78
  security_control_count: int = 0
73
79
 
74
80
  # get all controls by catalog
@@ -638,6 +638,9 @@ class ScannerIntegration(ABC):
638
638
  self.is_component: bool = is_component
639
639
  if self.is_component:
640
640
  self.component = regscale_models.Component.get_object(self.plan_id)
641
+ self.parent_module: str = regscale_models.Component.get_module_string()
642
+ else:
643
+ self.parent_module: str = regscale_models.SecurityPlan.get_module_string()
641
644
  self.components: ThreadSafeList[Any] = ThreadSafeList()
642
645
  self.asset_map_by_identifier: ThreadSafeDict[str, regscale_models.Asset] = ThreadSafeDict()
643
646
  self.software_to_create: ThreadSafeList[regscale_models.SoftwareInventory] = ThreadSafeList()
@@ -655,16 +658,17 @@ class ScannerIntegration(ABC):
655
658
  self.implementation_option_map: ThreadSafeDict[str, int] = ThreadSafeDict()
656
659
  self.control_implementation_map: ThreadSafeDict[int, regscale_models.ControlImplementation] = ThreadSafeDict()
657
660
 
658
- self.control_implementation_id_map = regscale_models.ControlImplementation.get_control_label_map_by_plan(
659
- plan_id=plan_id
661
+ self.control_implementation_id_map = regscale_models.ControlImplementation.get_control_label_map_by_parent(
662
+ parent_id=self.plan_id, parent_module=self.parent_module
660
663
  )
661
664
  self.control_map = {v: k for k, v in self.control_implementation_id_map.items()}
662
665
  self.existing_issue_ids_by_implementation_map = regscale_models.Issue.get_open_issues_ids_by_implementation_id(
663
- plan_id=plan_id
666
+ plan_id=self.plan_id, is_component=self.is_component
664
667
  ) # GraphQL Call
665
- self.control_id_to_implementation_map = regscale_models.ControlImplementation.get_control_id_map_by_plan(
666
- plan_id=plan_id
668
+ self.control_id_to_implementation_map = regscale_models.ControlImplementation.get_control_id_map_by_parent(
669
+ parent_id=self.plan_id, parent_module=self.parent_module
667
670
  )
671
+
668
672
  self.cci_to_control_map: ThreadSafeDict[str, set[int]] = ThreadSafeDict()
669
673
  self._no_ccis: bool = False
670
674
  self.cci_to_control_map_lock: threading.Lock = threading.Lock()
@@ -800,11 +804,7 @@ class ScannerIntegration(ABC):
800
804
  getattr(x, self.asset_identifier_field): x
801
805
  for x in regscale_models.Asset.get_all_by_parent(
802
806
  parent_id=self.plan_id,
803
- parent_module=(
804
- regscale_models.Component.get_module_string()
805
- if self.is_component
806
- else regscale_models.SecurityPlan.get_module_string()
807
- ),
807
+ parent_module=self.parent_module,
808
808
  )
809
809
  }
810
810
 
@@ -817,7 +817,7 @@ class ScannerIntegration(ABC):
817
817
  """
818
818
  all_issues = regscale_models.Issue.get_all_by_parent(
819
819
  parent_id=self.plan_id,
820
- parent_module=regscale_models.SecurityPlan.get_module_string(),
820
+ parent_module=self.parent_module,
821
821
  )
822
822
  return {issue.integrationFindingId: issue for issue in all_issues}
823
823
 
@@ -1094,11 +1094,7 @@ class ScannerIntegration(ABC):
1094
1094
  otherTrackingNumber=asset.other_tracking_number or asset.identifier,
1095
1095
  assetOwnerId=asset.asset_owner_id or "Unknown",
1096
1096
  parentId=component.id if component else self.plan_id,
1097
- parentModule=(
1098
- regscale_models.Component.get_module_string()
1099
- if component or self.is_component
1100
- else regscale_models.SecurityPlan.get_module_string()
1101
- ),
1097
+ parentModule=self.parent_module,
1102
1098
  assetType=asset.asset_type,
1103
1099
  dateLastUpdated=asset.date_last_updated or get_current_datetime(),
1104
1100
  status=asset.status,
@@ -1324,9 +1320,7 @@ class ScannerIntegration(ABC):
1324
1320
 
1325
1321
  :rtype: None
1326
1322
  """
1327
- regscale_models.Asset.get_all_by_parent(
1328
- parent_id=self.plan_id, parent_module=regscale_models.SecurityPlan.get_module_string()
1329
- )
1323
+ regscale_models.Asset.get_all_by_parent(parent_id=self.plan_id, parent_module=self.parent_module)
1330
1324
 
1331
1325
  def _create_process_function(self, loading_assets: TaskID) -> Callable[[IntegrationAsset], bool]:
1332
1326
  """
@@ -1600,11 +1594,7 @@ class ScannerIntegration(ABC):
1600
1594
 
1601
1595
  # Update all fields
1602
1596
  issue.parentId = self.plan_id
1603
- issue.parentModule = (
1604
- regscale_models.Component.get_module_string()
1605
- if self.is_component
1606
- else regscale_models.SecurityPlan.get_module_string()
1607
- )
1597
+ issue.parentModule = self.parent_module
1608
1598
  issue.vulnerabilityId = finding.vulnerability_id
1609
1599
  issue.title = issue_title
1610
1600
  issue.dateCreated = finding.date_created
@@ -2159,7 +2149,7 @@ class ScannerIntegration(ABC):
2159
2149
  # Get all existing POAM IDs and find the maximum
2160
2150
  issues: List[regscale_models.Issue] = regscale_models.Issue.get_all_by_parent(
2161
2151
  parent_id=self.plan_id,
2162
- parent_module=regscale_models.SecurityPlan.get_module_string(),
2152
+ parent_module=self.parent_module,
2163
2153
  )
2164
2154
  self._max_poam_id = max(
2165
2155
  (
@@ -2184,11 +2174,7 @@ class ScannerIntegration(ABC):
2184
2174
  """
2185
2175
  scan_history = regscale_models.ScanHistory(
2186
2176
  parentId=self.plan_id,
2187
- parentModule=(
2188
- regscale_models.Component.get_module_string()
2189
- if self.is_component
2190
- else regscale_models.SecurityPlan.get_module_string()
2191
- ),
2177
+ parentModule=self.parent_module,
2192
2178
  scanningTool=self.title,
2193
2179
  scanDate=self.scan_date if self.scan_date else get_current_datetime(),
2194
2180
  createdById=self.assessor_id,
@@ -2309,11 +2295,7 @@ class ScannerIntegration(ABC):
2309
2295
  description=finding.description,
2310
2296
  dateLastUpdated=finding.date_last_updated,
2311
2297
  parentId=self.plan_id,
2312
- parentModule=(
2313
- regscale_models.Component.get_module_string()
2314
- if self.is_component
2315
- else regscale_models.SecurityPlan.get_module_string()
2316
- ),
2298
+ parentModule=self.parent_module,
2317
2299
  dns=asset.fqdn or "unknown",
2318
2300
  status=regscale_models.VulnerabilityStatus.Open,
2319
2301
  ipAddress=finding.ip_address or asset.ipAddress or "",
@@ -2424,7 +2406,7 @@ class ScannerIntegration(ABC):
2424
2406
 
2425
2407
  # Get all vulnerabilities for this security plan
2426
2408
  all_vulnerabilities: list[regscale_models.Vulnerability] = regscale_models.Vulnerability.get_all_by_parent(
2427
- parent_id=self.plan_id, parent_module=regscale_models.SecurityPlan.get_module_string()
2409
+ parent_id=self.plan_id, parent_module=self.parent_module
2428
2410
  )
2429
2411
 
2430
2412
  # Pre-filter vulnerabilities that are not in current set
@@ -1,9 +1,54 @@
1
1
  {
2
2
  "title": "CISA Catalog of Known Exploited Vulnerabilities",
3
- "catalogVersion": "2025.07.22",
4
- "dateReleased": "2025-07-22T17:01:26.7198Z",
5
- "count": 1388,
3
+ "catalogVersion": "2025.07.28",
4
+ "dateReleased": "2025-07-28T14:00:14.6746Z",
5
+ "count": 1391,
6
6
  "vulnerabilities": [
7
+ {
8
+ "cveID": "CVE-2023-2533",
9
+ "vendorProject": "PaperCut",
10
+ "product": "NG\/MF",
11
+ "vulnerabilityName": "PaperCut NG\/MF Cross-Site Request Forgery (CSRF) Vulnerability",
12
+ "dateAdded": "2025-07-28",
13
+ "shortDescription": "PaperCut NG\/MF contains a cross-site request forgery (CSRF) vulnerability, which, under specific conditions, could potentially enable an attacker to alter security settings or execute arbitrary code. ",
14
+ "requiredAction": "Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.",
15
+ "dueDate": "2025-08-18",
16
+ "knownRansomwareCampaignUse": "Unknown",
17
+ "notes": "https:\/\/www.papercut.com\/kb\/Main\/SecurityBulletinJune2023 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2023-2533",
18
+ "cwes": [
19
+ "CWE-352"
20
+ ]
21
+ },
22
+ {
23
+ "cveID": "CVE-2025-20337",
24
+ "vendorProject": "Cisco",
25
+ "product": "Identity Services Engine",
26
+ "vulnerabilityName": "Cisco Identity Services Engine Injection Vulnerability",
27
+ "dateAdded": "2025-07-28",
28
+ "shortDescription": "Cisco Identity Services Engine contains an injection vulnerability in a specific API of Cisco ISE and Cisco ISE-PIC due to insufficient validation of user-supplied input allowing an attacker to exploit this vulnerability by submitting a crafted API request. Successful exploitation could allow an attacker to perform remote code execution and obtaining root privileges on an affected device.",
29
+ "requiredAction": "Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.",
30
+ "dueDate": "2025-08-18",
31
+ "knownRansomwareCampaignUse": "Unknown",
32
+ "notes": "https:\/\/sec.cloudapps.cisco.com\/security\/center\/content\/CiscoSecurityAdvisory\/cisco-sa-ise-unauth-rce-ZAd2GnJ6 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-20337",
33
+ "cwes": [
34
+ "CWE-74"
35
+ ]
36
+ },
37
+ {
38
+ "cveID": "CVE-2025-20281",
39
+ "vendorProject": "Cisco",
40
+ "product": "Identity Services Engine",
41
+ "vulnerabilityName": "Cisco Identity Services Engine Injection Vulnerability",
42
+ "dateAdded": "2025-07-28",
43
+ "shortDescription": "Cisco Identity Services Engine contains an injection vulnerability in a specific API of Cisco ISE and Cisco ISE-PIC due to insufficient validation of user-supplied input allowing an attacker to exploit this vulnerability by submitting a crafted API request. Successful exploitation could allow an attacker to perform remote code execution and obtaining root privileges on an affected device.",
44
+ "requiredAction": "Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.",
45
+ "dueDate": "2025-08-18",
46
+ "knownRansomwareCampaignUse": "Unknown",
47
+ "notes": "https:\/\/sec.cloudapps.cisco.com\/security\/center\/content\/CiscoSecurityAdvisory\/cisco-sa-ise-unauth-rce-ZAd2GnJ6 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-20281",
48
+ "cwes": [
49
+ "CWE-74"
50
+ ]
51
+ },
7
52
  {
8
53
  "cveID": "CVE-2025-2775",
9
54
  "vendorProject": "SysAid",
@@ -73,7 +118,7 @@
73
118
  "shortDescription": "Microsoft SharePoint contains a code injection vulnerability that could allow an authorized attacker to execute code over a network. This vulnerability could be chained with CVE-2025-49706. The update for CVE-2025-53770 includes more robust protections than the update for CVE-2025-49704.",
74
119
  "requiredAction": "CISA recommends disconnecting public-facing versions of SharePoint Server that have reached their end-of-life (EOL) or end-of-service (EOS). For example, SharePoint Server 2013 and earlier versions are end-of-life and should be discontinued if still in use. For supported versions, please follow the mitigations according to CISA and vendor instructions. Adhere to the applicable BOD 22-01 guidance for cloud services or discontinue use of the product if mitigations are not available.",
75
120
  "dueDate": "2025-07-23",
76
- "knownRansomwareCampaignUse": "Unknown",
121
+ "knownRansomwareCampaignUse": "Known",
77
122
  "notes": "CISA Mitigation Instructions: https:\/\/www.cisa.gov\/news-events\/alerts\/2025\/07\/20\/microsoft-releases-guidance-exploitation-sharepoint-vulnerability-cve-2025-53770; https:\/\/www.microsoft.com\/en-us\/security\/blog\/2025\/07\/22\/disrupting-active-exploitation-of-on-premises-sharepoint-vulnerabilities\/ ; https:\/\/msrc.microsoft.com\/update-guide\/vulnerability\/CVE-2025-49704 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-49704",
78
123
  "cwes": [
79
124
  "CWE-94"
@@ -88,8 +133,8 @@
88
133
  "shortDescription": "Microsoft SharePoint contains an improper authentication vulnerability that allows an authorized attacker to perform spoofing over a network. Successfully exploitation could allow an attacker to view sensitive information and make some changes to disclosed information. This vulnerability could be chained with CVE-2025-49704. The update for CVE-2025-53771 includes more robust protections than the update for CVE-2025-49706.",
89
134
  "requiredAction": "CISA recommends disconnecting public-facing versions of SharePoint Server that have reached their end-of-life (EOL) or end-of-service (EOS). For example, SharePoint Server 2013 and earlier versions are end-of-life and should be discontinued if still in use. For supported versions, please follow the mitigations according to CISA and vendor instructions. Adhere to the applicable BOD 22-01 guidance for cloud services or discontinue use of the product if mitigations are not available.",
90
135
  "dueDate": "2025-07-23",
91
- "knownRansomwareCampaignUse": "Unknown",
92
- "notes": "CISA Mitigation Instructions: https:\/\/www.cisa.gov\/news-events\/alerts\/2025\/07\/20\/microsoft-releases-guidance-exploitation-sharepoint-vulnerability-cve-2025-53770; https:\/\/www.microsoft.com\/en-us\/security\/blog\/2025\/07\/22\/disrupting-active-exploitation-of-on-premises-sharepoint-vulnerabilities\/ ; https:\/\/msrc.microsoft.com\/update-guide\/vulnerability\/CVE-2025-49706 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-49706",
136
+ "knownRansomwareCampaignUse": "Known",
137
+ "notes": "CISA Mitigation Instructions: https:\/\/www.cisa.gov\/news-events\/alerts\/2025\/07\/20\/microsoft-releases-guidance-exploitation-sharepoint-vulnerability-cve-2025-53770 ; https:\/\/www.microsoft.com\/en-us\/security\/blog\/2025\/07\/22\/disrupting-active-exploitation-of-on-premises-sharepoint-vulnerabilities\/ ; https:\/\/msrc.microsoft.com\/update-guide\/vulnerability\/CVE-2025-49706 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-49706",
93
138
  "cwes": [
94
139
  "CWE-287"
95
140
  ]
@@ -1,14 +1,13 @@
1
1
  """Container Scan Abstract"""
2
2
 
3
3
  import ast
4
- import csv
5
4
  import json
6
5
  import logging
7
6
  import re
8
7
  import shutil
9
8
  from abc import ABC, abstractmethod
10
9
  from collections import namedtuple
11
- from datetime import datetime, timedelta
10
+ from datetime import datetime
12
11
  from os import PathLike
13
12
  from typing import TYPE_CHECKING, Any, Callable, Generator, Iterator, List, Optional, Sequence, TextIO, Tuple, Union
14
13
 
@@ -18,7 +17,6 @@ if TYPE_CHECKING:
18
17
  from pathlib import Path
19
18
 
20
19
  import click
21
- import requests
22
20
  import xmltodict
23
21
  from openpyxl.reader.excel import load_workbook
24
22
 
@@ -32,11 +30,10 @@ from regscale.core.app.utils.app_utils import (
32
30
  get_current_datetime,
33
31
  )
34
32
  from regscale.core.app.utils.parser_utils import safe_datetime_str
35
- from regscale.core.app.utils.report_utils import ReportGenerator
36
33
  from regscale.integrations.scanner_integration import ScannerIntegration
37
34
  from regscale.models import IssueStatus, Metadata, regscale_models
38
35
  from regscale.models.app_models.mapping import Mapping
39
- from regscale.models.regscale_models import Asset, File, Issue, Vulnerability
36
+ from regscale.models.regscale_models import Asset, File, Vulnerability
40
37
 
41
38
  logger = logging.getLogger(__name__)
42
39
 
@@ -74,6 +71,7 @@ class FlatFileIntegration(ScannerIntegration):
74
71
  "Low": regscale_models.IssueSeverity.Low,
75
72
  }
76
73
  super().__init__(plan_id=plan_id, **kwargs)
74
+ self.is_component = kwargs.get("is_component", False)
77
75
 
78
76
  def set_asset_identifier_field(self, asset_identifier_field: str) -> None:
79
77
  """
@@ -130,10 +128,16 @@ class FlatFileImporter(ABC):
130
128
  "Medium": regscale_models.IssueSeverity.Moderate,
131
129
  "Low": regscale_models.IssueSeverity.Low,
132
130
  }
133
- if "parent_id" not in kwargs and "regscale_ssp_id" in kwargs:
134
- kwargs["parent_id"] = kwargs["regscale_ssp_id"]
135
- kwargs["parent_module"] = "securityplans"
136
- kwargs["plan_id"] = kwargs["regscale_ssp_id"]
131
+
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()
140
+
137
141
  if "app" not in kwargs:
138
142
  kwargs["app"] = Application()
139
143
  # empty generator
@@ -156,7 +160,8 @@ class FlatFileImporter(ABC):
156
160
  "mapping",
157
161
  "ignore_validation",
158
162
  "header_line_number",
159
- "regscale_ssp_id",
163
+ "is_component",
164
+ "object_id",
160
165
  "plan_id",
161
166
  "disable_mapping",
162
167
  "mappings_path",
@@ -188,7 +193,8 @@ class FlatFileImporter(ABC):
188
193
  }
189
194
  self.create_epoch = str(int(creation_date(self.attributes.file_path)))
190
195
  flat_int = FlatFileIntegration(
191
- plan_id=self.attributes.plan_id or self.attributes.regscale_ssp_id or self.attributes.parent_id,
196
+ plan_id=self.attributes.plan_id or self.attributes.object_id or self.attributes.parent_id,
197
+ is_component=self.attributes.is_component,
192
198
  asset_identifier_field=self.asset_identifier_field,
193
199
  finding_severity_map=self.finding_severity_map,
194
200
  )
@@ -206,13 +212,15 @@ class FlatFileImporter(ABC):
206
212
  elif isinstance(self.data["vulns"], list):
207
213
  flat_int.num_findings_to_process = len(self.data["vulns"])
208
214
  flat_int.sync_assets(
209
- plan_id=self.attributes.parent_id,
215
+ plan_id=self.attributes.plan_id,
216
+ is_component=self.attributes.is_component,
210
217
  integration_assets=self.integration_assets,
211
218
  title=self.attributes.name,
212
219
  asset_count=flat_int.num_assets_to_process,
213
220
  )
214
221
  flat_int.sync_findings(
215
- plan_id=self.attributes.parent_id,
222
+ plan_id=self.attributes.plan_id,
223
+ is_component=self.attributes.is_component,
216
224
  integration_findings=self.integration_findings,
217
225
  title=self.attributes.name,
218
226
  finding_count=flat_int.num_findings_to_process,
@@ -319,7 +327,7 @@ class FlatFileImporter(ABC):
319
327
  asset_type=asset.assetType,
320
328
  asset_owner_id=asset.assetOwnerId,
321
329
  parent_id=self.attributes.parent_id,
322
- parent_module=regscale_models.SecurityPlan.get_module_slug(),
330
+ parent_module=self.attributes.parent_module,
323
331
  asset_category=asset.assetCategory,
324
332
  date_last_updated=asset.dateLastUpdated,
325
333
  status=asset.status,
@@ -651,7 +659,7 @@ class FlatFileImporter(ABC):
651
659
  import_name: str,
652
660
  file_types: Union[str, list[str]],
653
661
  folder_path: PathLike[str],
654
- regscale_ssp_id: int,
662
+ object_id: int,
655
663
  scan_date: datetime,
656
664
  mappings_path: Union[PathLike[str], Path],
657
665
  disable_mapping: bool,
@@ -668,7 +676,7 @@ class FlatFileImporter(ABC):
668
676
  :param str import_name: The name of the import type
669
677
  :param Union[str, list[str]] file_types: The file types to glob and import, e.g. ".csv" or [".csv", ".xlsx"]
670
678
  :param PathLike[str] folder_path: The folder path to import from
671
- :param int regscale_ssp_id: The RegScale SSP ID
679
+ :param int object_id: The RegScale SSP ID
672
680
  :param datetime scan_date: The date of the scan
673
681
  :param Union[PathLike[str], Path] mappings_path: The path to the mappings file
674
682
  :param bool disable_mapping: Whether to disable custom mappings
@@ -685,9 +693,16 @@ class FlatFileImporter(ABC):
685
693
  if s3_bucket:
686
694
  download_from_s3(s3_bucket, s3_prefix, folder_path, aws_profile)
687
695
  app = Application()
688
- if not validate_regscale_object(regscale_ssp_id, "securityplans"):
689
- app.logger.warning("SSP #%i is not a valid RegScale Security Plan.", regscale_ssp_id)
696
+
697
+ # Validate the parent_id is a valid RegScale object
698
+ is_component = kwargs.get("is_component", False)
699
+ if is_component and not validate_regscale_object(object_id, "components"):
700
+ app.logger.warning("Component #%i is not a valid RegScale Component.", object_id)
701
+ return
702
+ elif not is_component and not validate_regscale_object(object_id, "securityplans"):
703
+ app.logger.warning("SSP #%i is not a valid RegScale Security Plan.", object_id)
690
704
  return
705
+
691
706
  if not scan_date or not FlatFileImporter.check_date_format(scan_date):
692
707
  scan_date = datetime.now()
693
708
  if isinstance(file_types, str):
@@ -705,7 +720,7 @@ class FlatFileImporter(ABC):
705
720
  import_type(
706
721
  name=import_name,
707
722
  file_path=str(file),
708
- regscale_ssp_id=regscale_ssp_id,
723
+ object_id=object_id,
709
724
  scan_date=scan_date,
710
725
  mappings_path=mappings_path,
711
726
  disable_mapping=disable_mapping,
@@ -554,8 +554,14 @@ class SendReminders:
554
554
  )
555
555
 
556
556
  # update api pool limits to max_thread count from init.yaml
557
- api.pool_connections = config["maxThreads"]
558
- api.pool_maxsize = config["maxThreads"]
557
+ max_threads = config.get("maxThreads", 100)
558
+ if not isinstance(max_threads, int):
559
+ try:
560
+ max_threads = int(max_threads)
561
+ except (ValueError, TypeError):
562
+ max_threads = 100
563
+ api.pool_connections = max_threads
564
+ api.pool_maxsize = max_threads
559
565
 
560
566
  # get assigned threads
561
567
  for i in range(len(threads)):